@@ -50,37 +50,6 @@ namespace sqlite_orm {
5050            explicit  connection_holder (const  connection_holder& other, std::true_type /* openedForeverHint*/  ) :
5151                _control{true }, dbArgs{other.dbArgs }, _didOpenDb{other._didOpenDb } {}
5252
53-             /*   
54-                 Open from a single-threaded context. 
55-              */  
56-             void  _do_open () {
57-                 int  openFlags = db_open_mode_to_int_flags (this ->dbArgs .open_mode );
58- #if  SQLITE_VERSION_NUMBER >= 3037002
59-                 openFlags |= SQLITE_OPEN_EXRESCODE;
60- #endif 
61- 
62-                 const  int  rc = sqlite3_open_v2 (this ->dbArgs .filename .c_str (),
63-                                                &_control.db ,
64-                                                openFlags,
65-                                                this ->dbArgs .vfs_name .c_str ());
66- 
67-                 if  (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY /* possible, but unexpected*/   {
68-                     throw_translated_sqlite_error (rc);
69-                 }
70-             }
71- 
72-             /*   
73-                 Close from a single-threaded context. 
74-              */  
75-             void  _do_close () {
76-                 const  int  rc = sqlite3_close_v2 (_control.db );
77-                 if  (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY {
78-                     throw_translated_sqlite_error (_control.db );
79-                 } else  {
80-                     _control.db  = nullptr ;
81-                 }
82-             }
83- 
8453            /*   
8554                Open the database once and for all from a single-threaded context when it should be opened permanently. 
8655             */  
@@ -109,47 +78,19 @@ namespace sqlite_orm {
10978                _do_close ();
11079            }
11180
81+             /* 
82+                 Retain the database handle if the database is already open, `nullptr` otherwise. 
83+              */  
11284            sqlite3* retain_if_open () {
113-                 //  optional marginal optimization for permanently opened connections;
114-                 if  (_control.openedForeverHint ) {
115- #ifdef  SQLITE_ORM_CONTRACTS_SUPPORTED
116-                     contract_assert (_control.db );
117- #endif 
118-                     return  _control.db ;
119-                 }
120- 
121-                 //  required fast path: if connection is already open, just increment counter;
122-                 //  it is required otherwise 'retain if open' would be useless;
123-                 //  with respect to performance,
124-                 //  this can make a difference while a transaction is active where all things happen in memory only;
125-                 //  it makes a difference if the `_didOpenDb` callback has a lot of work to do.
126-                 if  (int  currentCount = _control.retainCount .load (std::memory_order_acquire)) {
127-                     do  {
128-                         if  (_control.retainCount .compare_exchange_weak (currentCount,
129-                                                                        currentCount + 1 ,
130-                                                                        std::memory_order_release,
131-                                                                        std::memory_order_acquire)) {
132-                             //  successfully incremented, connection is guaranteed to be open
133-                             return  _control.db ;
134-                         }
135-                         //  CAS failed - retry
136-                     } while  (currentCount > 0 );
137-                 }
138-                 //  test for recursion from the same thread
139-                 else  /* currentCount==0*/   {
140-                     const  std::thread::id threadId = _control.initializingThreadId .load (std::memory_order_acquire);
141-                     if  (threadId != std::thread::id{} && std::this_thread::get_id () == threadId)
142-                         SQLITE_ORM_CPP_UNLIKELY {
143-                         return  _control.db ;
144-                     }
145-                 }
146- 
147-                 return  nullptr ;
85+                 return  _try_retain_if_open (false );
14886            }
14987
88+             /* 
89+                 Retain the database handle if the database is already open, otherwise open the database. 
90+              */  
15091            sqlite3* retain () {
151-                 //  optional fast path: if connection is already open, just increment  counter;
152-                 if  (sqlite3* db = retain_if_open ( )) {
92+                 //  optional fast path: if connection is already open, just try incrementing the  counter;
93+                 if  (sqlite3* db = _try_retain_if_open ( true )) {
15394                    return  db;
15495                }
15596
@@ -177,7 +118,7 @@ namespace sqlite_orm {
177118            }
178119
179120            void  release () {
180-                 //  optional marginal  optimization for permanently opened connections;
121+                 //  optional optimization for permanently opened connections;
181122                if  (_control.openedForeverHint ) {
182123#ifdef  SQLITE_ORM_CONTRACTS_SUPPORTED
183124                    contract_assert (_control.db );
@@ -186,7 +127,7 @@ namespace sqlite_orm {
186127                }
187128
188129                //  test for recursion from the same thread;
189-                 //  testing against an empty thread id is sufficient because recursion is only possible while calling  the `_didOpenDb` callback in `retain()`
130+                 //  testing against an empty thread id is sufficient because recursion is only possible while the `_didOpenDb` callback is executing  in `retain()`
190131                if  (_control.initializingThreadId .load (std::memory_order_acquire) != std::thread::id{})
191132                    SQLITE_ORM_CPP_UNLIKELY {
192133                    return ;
@@ -205,18 +146,83 @@ namespace sqlite_orm {
205146                }
206147            }
207148
149+             /*   
150+                 Open from a single-threaded context. 
151+              */  
152+             void  _do_open () {
153+                 int  openFlags = db_open_mode_to_int_flags (this ->dbArgs .open_mode );
154+ #if  SQLITE_VERSION_NUMBER >= 3037002
155+                 openFlags |= SQLITE_OPEN_EXRESCODE;
156+ #endif 
157+ 
158+                 const  int  rc = sqlite3_open_v2 (this ->dbArgs .filename .c_str (),
159+                                                &_control.db ,
160+                                                openFlags,
161+                                                this ->dbArgs .vfs_name .c_str ());
162+ 
163+                 if  (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY /* possible, but unexpected*/   {
164+                     throw_translated_sqlite_error (rc);
165+                 }
166+             }
167+ 
168+             /*   
169+                 Close from a single-threaded context. 
170+              */  
171+             void  _do_close () {
172+                 const  int  rc = sqlite3_close_v2 (_control.db );
173+                 if  (rc != SQLITE_OK) SQLITE_ORM_CPP_UNLIKELY {
174+                     throw_translated_sqlite_error (_control.db );
175+                 } else  {
176+                     _control.db  = nullptr ;
177+                 }
178+             }
179+ 
180+             sqlite3* _try_retain_if_open (const  bool  yieldIfContended) {
181+                 //  optional optimization for permanently opened connections;
182+                 if  (_control.openedForeverHint ) {
183+ #ifdef  SQLITE_ORM_CONTRACTS_SUPPORTED
184+                     contract_assert (_control.db );
185+ #endif 
186+                     return  _control.db ;
187+                 }
188+ 
189+                 if  (int  currentCount = _control.retainCount .load (std::memory_order_relaxed)) {
190+                     do  {
191+                         if  (_control.retainCount .compare_exchange_weak (currentCount,
192+                                                                        currentCount + 1 ,
193+                                                                        std::memory_order_release,
194+                                                                        std::memory_order_acquire)) {
195+                             //  successfully incremented, connection is guaranteed to be open
196+                             return  _control.db ;
197+                         }
198+                         //  CAS failed due to contention;
199+                         //  1. !yieldIfContended (compete) : retry until successful or count reaches zero because another thread closed the database;
200+                         //  2. yieldIfContended: do not try again; it does not have to be lock-free at all costs, which avoids CPUs competing for incrementation.
201+                     } while  (!yieldIfContended && currentCount > 0 );
202+                 }
203+                 //  test for recursion from the same thread
204+                 else  /* currentCount == 0*/   {
205+                     const  std::thread::id threadId = _control.initializingThreadId .load (std::memory_order_acquire);
206+                     if  (threadId != std::thread::id{} && std::this_thread::get_id () == threadId)
207+                         SQLITE_ORM_CPP_UNLIKELY {
208+                         return  _control.db ;
209+                     }
210+                 }
211+ 
212+                 return  nullptr ;
213+             }
214+ 
208215            //  note: members of the `control_block` are deliberately put on the same cache-line
209216            SQLITE_ORM_MSVC_SUPPRESS_OVERALIGNMENT (alignas (polyfill::hardware_destructive_interference_size))
210217            struct  control_block  {
211-                 //  the optimization gain is very small;
212-                 //  at some design point it served as a flag to not use a mutex at all;
213-                 //  now it merely saves all the atomic operations, which actually perform without noticeable difference;
214-                 //  however it may be kept for conveying logic or future optimizations.
215-                 const  bool  openedForeverHint = false ;
218+                 //  Optional optimization hint that also serves to convey logic.
219+                 //  in a test scenario involving a tight retain()/releae() loop from multiple threads the performance gain is outstanding;
220+                 //  in a real-world scenario it merely saves all the atomic operations and the CPU cache updates they entail;
221+                 bool  openedForeverHint = false ;
216222                std::atomic_int retainCount{};
217223                //  `db` synchronizes with `retainCount`
218224                orm_gsl::owner<sqlite3*> db = nullptr ;
219-                 //  we don't know what the user-provided `on_open` callback might do, so we need to track recursion;
225+                 //  we don't know what the user-provided `on_open` callback might do, so we need to track recursion during the `_didOpenDb` callback ;
220226                std::atomic<std::thread::id> initializingThreadId{};
221227            } _control;
222228
0 commit comments