1111
1212#include < map>
1313#include < vector>
14+ #include < mutex>
1415
1516/* *
1617 * Forward declarations.
1718 */
1819struct Database ;
1920struct Iterator ;
2021static void iterator_close_do (napi_env env, Iterator* iterator, napi_value cb);
22+ static leveldb::Status threadsafe_open (const leveldb::Options &options,
23+ bool multithreading,
24+ Database &db_instance);
25+ static leveldb::Status threadsafe_close (Database &db_instance);
2126
2227/* *
23- * Macros.
28+ * Global declarations for multi-threaded access. These are not context-aware
29+ * by definition and is specifically to allow for cross thread access to the
30+ * single database handle.
2431 */
32+ struct LevelDbHandle
33+ {
34+ leveldb::DB *db;
35+ size_t open_handle_count;
36+ };
37+ static std::mutex handles_mutex;
38+ // only access this when protected by the handles_mutex!
39+ static std::map<std::string, LevelDbHandle> db_handles;
2540
41+ /* *
42+ * Macros.
43+ */
2644#define NAPI_DB_CONTEXT () \
2745 Database* database = NULL ; \
2846 NAPI_STATUS_THROWS (napi_get_value_external(env, argv[0 ], (void **)&database));
@@ -495,19 +513,21 @@ struct Database {
495513
496514 ~Database () {
497515 if (db_ != NULL ) {
498- delete db_;
499- db_ = NULL ;
516+ threadsafe_close (*this );
500517 }
501518 }
502519
503520 leveldb::Status Open (const leveldb::Options& options,
504- const char * location) {
505- return leveldb::DB::Open (options, location, &db_);
521+ const std::string &location,
522+ bool multithreading) {
523+ location_ = location;
524+ return threadsafe_open (options, multithreading, *this );
506525 }
507526
508527 void CloseDatabase () {
509- delete db_;
510- db_ = NULL ;
528+ if (db_ != NULL ) {
529+ threadsafe_close (*this );
530+ }
511531 if (blockCache_) {
512532 delete blockCache_;
513533 blockCache_ = NULL ;
@@ -600,8 +620,66 @@ struct Database {
600620
601621private:
602622 uint32_t priorityWork_;
623+ std::string location_;
624+
625+ // for separation of concerns the threadsafe functionality was kept at the global
626+ // level and made a friend so it is explict where the threadsafe boundary exists
627+ friend leveldb::Status threadsafe_open (const leveldb::Options &options,
628+ bool multithreading,
629+ Database &db_instance);
630+ friend leveldb::Status threadsafe_close (Database &db_instance);
603631};
604632
633+
634+ leveldb::Status threadsafe_open (const leveldb::Options &options,
635+ bool multithreading,
636+ Database &db_instance) {
637+ // Bypass lock and handles if multithreading is disabled
638+ if (!multithreading) {
639+ return leveldb::DB::Open (options, db_instance.location_ , &db_instance.db_ );
640+ }
641+
642+ std::unique_lock<std::mutex> lock (handles_mutex);
643+
644+ auto it = db_handles.find (db_instance.location_ );
645+ if (it == db_handles.end ()) {
646+ // Database not opened yet for this location, unless it was with
647+ // multithreading disabled, in which case we're expected to fail here.
648+ LevelDbHandle handle = {nullptr , 0 };
649+ leveldb::Status status = leveldb::DB::Open (options, db_instance.location_ , &handle.db );
650+
651+ if (status.ok ()) {
652+ handle.open_handle_count ++;
653+ db_instance.db_ = handle.db ;
654+ db_handles[db_instance.location_ ] = handle;
655+ }
656+
657+ return status;
658+ }
659+
660+ ++(it->second .open_handle_count );
661+ db_instance.db_ = it->second .db ;
662+
663+ return leveldb::Status::OK ();
664+ }
665+
666+ leveldb::Status threadsafe_close (Database &db_instance) {
667+ std::unique_lock<std::mutex> lock (handles_mutex);
668+
669+ auto it = db_handles.find (db_instance.location_ );
670+ if (it == db_handles.end ()) {
671+ // Was not opened with multithreading enabled
672+ delete db_instance.db_ ;
673+ } else if (--(it->second .open_handle_count ) == 0 ) {
674+ delete it->second .db ;
675+ db_handles.erase (it);
676+ }
677+
678+ // ensure db_ pointer is nullified in Database instance
679+ db_instance.db_ = NULL ;
680+ return leveldb::Status::OK ();
681+ }
682+
605683/* *
606684 * Base worker class for doing async work that defers closing the database.
607685 */
@@ -974,13 +1052,15 @@ struct OpenWorker final : public BaseWorker {
9741052 const bool createIfMissing,
9751053 const bool errorIfExists,
9761054 const bool compression,
1055+ const bool multithreading,
9771056 const uint32_t writeBufferSize,
9781057 const uint32_t blockSize,
9791058 const uint32_t maxOpenFiles,
9801059 const uint32_t blockRestartInterval,
9811060 const uint32_t maxFileSize)
9821061 : BaseWorker(env, database, callback, " classic_level.db.open" ),
983- location_ (location) {
1062+ location_ (location),
1063+ multithreading_(multithreading) {
9841064 options_.block_cache = database->blockCache_ ;
9851065 options_.filter_policy = database->filterPolicy_ ;
9861066 options_.create_if_missing = createIfMissing;
@@ -998,11 +1078,12 @@ struct OpenWorker final : public BaseWorker {
9981078 ~OpenWorker () {}
9991079
10001080 void DoExecute () override {
1001- SetStatus (database_->Open (options_, location_. c_str () ));
1081+ SetStatus (database_->Open (options_, location_, multithreading_ ));
10021082 }
10031083
10041084 leveldb::Options options_;
10051085 std::string location_;
1086+ bool multithreading_;
10061087};
10071088
10081089/* *
@@ -1017,6 +1098,7 @@ NAPI_METHOD(db_open) {
10171098 const bool createIfMissing = BooleanProperty (env, options, " createIfMissing" , true );
10181099 const bool errorIfExists = BooleanProperty (env, options, " errorIfExists" , false );
10191100 const bool compression = BooleanProperty (env, options, " compression" , true );
1101+ const bool multithreading = BooleanProperty (env, options, " multithreading" , false );
10201102
10211103 const uint32_t cacheSize = Uint32Property (env, options, " cacheSize" , 8 << 20 );
10221104 const uint32_t writeBufferSize = Uint32Property (env, options , " writeBufferSize" , 4 << 20 );
@@ -1031,7 +1113,8 @@ NAPI_METHOD(db_open) {
10311113 napi_value callback = argv[3 ];
10321114 OpenWorker* worker = new OpenWorker (env, database, callback, location,
10331115 createIfMissing, errorIfExists,
1034- compression, writeBufferSize, blockSize,
1116+ compression, multithreading,
1117+ writeBufferSize, blockSize,
10351118 maxOpenFiles, blockRestartInterval,
10361119 maxFileSize);
10371120 worker->Queue (env);
0 commit comments