From 7949764a770416b45cc441bb576d57e85e4b4a11 Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Fri, 31 Jul 2020 09:51:01 -0700 Subject: [PATCH 01/40] Cleanup unused ifdefs --- generate/templates/templates/nodegit.cc | 42 ++++++++----------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/generate/templates/templates/nodegit.cc b/generate/templates/templates/nodegit.cc index 6a3fa8ef4..f3c7f3ab3 100644 --- a/generate/templates/templates/nodegit.cc +++ b/generate/templates/templates/nodegit.cc @@ -23,37 +23,21 @@ #include "../include/convenient_hunk.h" #include "../include/filter_registry.h" -#if (NODE_MODULE_VERSION > 48) - v8::Local GetPrivate(v8::Local object, - v8::Local key) { - v8::Local value; - Nan::Maybe result = Nan::HasPrivate(object, key); - if (!(result.IsJust() && result.FromJust())) - return v8::Local(); - if (Nan::GetPrivate(object, key).ToLocal(&value)) - return value; +v8::Local GetPrivate(v8::Local object, v8::Local key) { + v8::Local value; + Nan::Maybe result = Nan::HasPrivate(object, key); + if (!(result.IsJust() && result.FromJust())) return v8::Local(); - } - - void SetPrivate(v8::Local object, - v8::Local key, - v8::Local value) { - if (value.IsEmpty()) - return; - Nan::SetPrivate(object, key, value); - } -#else - v8::Local GetPrivate(v8::Local object, - v8::Local key) { - return object->GetHiddenValue(key); - } + if (Nan::GetPrivate(object, key).ToLocal(&value)) + return value; + return v8::Local(); +} - void SetPrivate(v8::Local object, - v8::Local key, - v8::Local value) { - object->SetHiddenValue(key, value); - } -#endif +void SetPrivate(v8::Local object, v8::Local key, v8::Local value) { + if (value.IsEmpty()) + return; + Nan::SetPrivate(object, key, value); +} void LockMasterEnable(const FunctionCallbackInfo& info) { LockMaster::Enable(); From 33326b49493cce9b1a738ec22650232176b5c153 Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Fri, 31 Jul 2020 10:21:10 -0700 Subject: [PATCH 02/40] Guard initialization so that we only initialize our core libraries once --- generate/templates/templates/nodegit.cc | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/generate/templates/templates/nodegit.cc b/generate/templates/templates/nodegit.cc index f3c7f3ab3..8d1790afc 100644 --- a/generate/templates/templates/nodegit.cc +++ b/generate/templates/templates/nodegit.cc @@ -5,8 +5,8 @@ #include #include #include - #include +#include #include "../include/init_ssh2.h" #include "../include/lock_master.h" @@ -98,14 +98,26 @@ void OpenSSL_ThreadSetup() { CRYPTO_THREADID_set_callback(OpenSSL_IDCallback); } +// TODO initialize a thread pool per context. Replace uv_default_loop() with node::GetCurrentEventLoop(isolate); ThreadPool libgit2ThreadPool(10, uv_default_loop()); +std::once_flag libraryInitializedFlag; +std::mutex libraryInitializationMutex; + extern "C" void init(v8::Local target) { - // Initialize thread safety in openssl and libssh2 - OpenSSL_ThreadSetup(); - init_ssh2(); - // Initialize libgit2. - git_libgit2_init(); + { + // We only want to do initialization logic once, and we also want to prevent any thread from completely loading + // the module until initialization has occurred. + // All of this initialization logic ends up being shared. + const std::lock_guard lock(libraryInitializationMutex); + std::call_once(libraryInitializedFlag, []() { + // Initialize thread safety in openssl and libssh2 + OpenSSL_ThreadSetup(); + init_ssh2(); + // Initialize libgit2. + git_libgit2_init(); + }); + } Nan::HandleScope scope; From 5ad8682474d05e1258cfdad512aae85a0ccdc60b Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Fri, 31 Jul 2020 10:30:13 -0700 Subject: [PATCH 03/40] Use NAN_MODULE_INIT and prevent worker_threads from booting library Until node understands how to cleanup a native node module asynchronously, we cannot allow nodegit to operate in a worker thread or it will die on exit. Other concerns... Process reuse in Electron may be a problem without async cleanup being a real thing. --- generate/templates/templates/nodegit.cc | 2 +- generate/templates/templates/nodegit.js | 6 ++++++ package-lock.json | 6 +++--- package.json | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/generate/templates/templates/nodegit.cc b/generate/templates/templates/nodegit.cc index 8d1790afc..447bdb4fb 100644 --- a/generate/templates/templates/nodegit.cc +++ b/generate/templates/templates/nodegit.cc @@ -104,7 +104,7 @@ ThreadPool libgit2ThreadPool(10, uv_default_loop()); std::once_flag libraryInitializedFlag; std::mutex libraryInitializationMutex; -extern "C" void init(v8::Local target) { +NAN_MODULE_INIT(init) { { // We only want to do initialization logic once, and we also want to prevent any thread from completely loading // the module until initialization has occurred. diff --git a/generate/templates/templates/nodegit.js b/generate/templates/templates/nodegit.js index c22d7d999..a580b4df7 100644 --- a/generate/templates/templates/nodegit.js +++ b/generate/templates/templates/nodegit.js @@ -1,7 +1,13 @@ var _ = require("lodash"); var util = require("util"); +var worker = require("worker_threads"); + var rawApi; +if (!worker.isMainThread || typeof importScripts === "function") { + throw new Error("NodeGit is currently not safe to run in a worker thread or web worker"); +} + // Attempt to load the production release first, if it fails fall back to the // debug release. try { diff --git a/package-lock.json b/package-lock.json index 6c7f169a1..cfaa4813f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3779,9 +3779,9 @@ "dev": true }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" }, "nanomatch": { "version": "1.2.13", diff --git a/package.json b/package.json index 36c09494c..18ba76965 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "got": "^10.7.0", "json5": "^2.1.0", "lodash": "^4.17.14", - "nan": "^2.14.0", + "nan": "^2.14.1", "node-gyp": "^4.0.0", "node-pre-gyp": "^0.13.0", "ramda": "^0.25.0", From 6167a10be3199ebf63283e07c886097a312f9379 Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Mon, 3 Aug 2020 15:10:58 -0700 Subject: [PATCH 04/40] Make LockMaster shareable across contexts --- .../templates/manual/include/lock_master.h | 40 +---------- generate/templates/manual/src/lock_master.cc | 40 ++++------- generate/templates/templates/nodegit.cc | 66 ++++--------------- test/runner.js | 8 --- test/tests/thread_safety.js | 65 ------------------ 5 files changed, 28 insertions(+), 191 deletions(-) delete mode 100644 test/tests/thread_safety.js diff --git a/generate/templates/manual/include/lock_master.h b/generate/templates/manual/include/lock_master.h index fde38825b..911760bd8 100644 --- a/generate/templates/manual/include/lock_master.h +++ b/generate/templates/manual/include/lock_master.h @@ -6,16 +6,7 @@ class LockMasterImpl; class LockMaster { -public: - enum Status { - Disabled = 0, - EnabledForAsyncOnly, - Enabled - }; - private: - static Status status; - LockMasterImpl *impl; template @@ -44,7 +35,7 @@ class LockMaster { // we lock on construction template LockMaster(bool asyncAction, const Types*... types) { - if((status == Disabled) || ((status == EnabledForAsyncOnly) && !asyncAction)) { + if(!asyncAction) { impl = NULL; return; } @@ -85,33 +76,8 @@ class LockMaster { } }; - static void Initialize(); - - // Enables the thread safety system - static void Enable() { - status = Enabled; - } - - static void SetStatus(Status status) { - LockMaster::status = status; - } - - static void Disable() { - status = Disabled; - } - - static Status GetStatus() { - return status; - } - - // Diagnostic information that can be provided to the JavaScript layer - // for a minimal level of testing - struct Diagnostics { - // this counts all stored mutexes - even if they are unlocked: - int storedMutexesCount; - }; - - static Diagnostics GetDiagnostics(); + static void InitializeGlobal(); + static void InitializeContext(); }; diff --git a/generate/templates/manual/src/lock_master.cc b/generate/templates/manual/src/lock_master.cc index 30679b534..553928177 100644 --- a/generate/templates/manual/src/lock_master.cc +++ b/generate/templates/manual/src/lock_master.cc @@ -36,7 +36,9 @@ class LockMasterImpl { static NAN_GC_CALLBACK(CleanupMutexes); public: - static void Initialize(); + static void InitializeGlobal(); + + static void InitializeContext(); // INSTANCE variables / methods @@ -53,7 +55,6 @@ class LockMasterImpl { static LockMasterImpl *CurrentLockMasterImpl() { return (LockMasterImpl *)uv_key_get(¤tLockMasterKey); } - static LockMaster::Diagnostics GetDiagnostics(); LockMasterImpl() { Register(); @@ -76,21 +77,16 @@ std::map LockMasterImpl::mutexes; uv_mutex_t LockMasterImpl::mapMutex; uv_key_t LockMasterImpl::currentLockMasterKey; -void LockMasterImpl::Initialize() { +void LockMasterImpl::InitializeGlobal() { uv_mutex_init(&mapMutex); uv_key_create(¤tLockMasterKey); +} + +void LockMasterImpl::InitializeContext() { Nan::AddGCEpilogueCallback(CleanupMutexes); } NAN_GC_CALLBACK(LockMasterImpl::CleanupMutexes) { - // skip cleanup if thread safety is disabled - // this means that turning thread safety on and then off - // could result in remaining mutexes - but they would get cleaned up - // if thread safety is turned on again - if (LockMaster::GetStatus() == LockMaster::Disabled) { - return; - } - uv_mutex_lock(&mapMutex); for (auto it = mutexes.begin(); it != mutexes.end(); ) @@ -113,8 +109,12 @@ NAN_GC_CALLBACK(LockMasterImpl::CleanupMutexes) { uv_mutex_unlock(&mapMutex); } -void LockMaster::Initialize() { - LockMasterImpl::Initialize(); +void LockMaster::InitializeGlobal() { + LockMasterImpl::InitializeGlobal(); +} + +void LockMaster::InitializeContext() { + LockMasterImpl::InitializeContext(); } std::vector LockMasterImpl::GetMutexes(int useCountDelta) { @@ -200,14 +200,6 @@ void LockMasterImpl::Unlock(bool releaseMutexes) { GetMutexes(releaseMutexes * -1); } -LockMaster::Diagnostics LockMasterImpl::GetDiagnostics() { - LockMaster::Diagnostics diagnostics; - uv_mutex_lock(&LockMasterImpl::mapMutex); - diagnostics.storedMutexesCount = mutexes.size(); - uv_mutex_unlock(&LockMasterImpl::mapMutex); - return diagnostics; -} - // LockMaster void LockMaster::ConstructorImpl() { @@ -226,10 +218,6 @@ void LockMaster::ObjectsToLockAdded() { impl->Lock(true); } -LockMaster::Diagnostics LockMaster::GetDiagnostics() { - return LockMasterImpl::GetDiagnostics(); -} - // LockMaster::TemporaryUnlock void LockMaster::TemporaryUnlock::ConstructorImpl() { @@ -242,5 +230,3 @@ void LockMaster::TemporaryUnlock::ConstructorImpl() { void LockMaster::TemporaryUnlock::DestructorImpl() { impl->Lock(false); } - -LockMaster::Status LockMaster::status = LockMaster::Disabled; diff --git a/generate/templates/templates/nodegit.cc b/generate/templates/templates/nodegit.cc index 447bdb4fb..3d742adb6 100644 --- a/generate/templates/templates/nodegit.cc +++ b/generate/templates/templates/nodegit.cc @@ -23,56 +23,24 @@ #include "../include/convenient_hunk.h" #include "../include/filter_registry.h" -v8::Local GetPrivate(v8::Local object, v8::Local key) { - v8::Local value; +using namespace v8; + +Local GetPrivate(Local object, Local key) { + Local value; Nan::Maybe result = Nan::HasPrivate(object, key); if (!(result.IsJust() && result.FromJust())) - return v8::Local(); + return Local(); if (Nan::GetPrivate(object, key).ToLocal(&value)) return value; - return v8::Local(); + return Local(); } -void SetPrivate(v8::Local object, v8::Local key, v8::Local value) { +void SetPrivate(Local object, Local key, Local value) { if (value.IsEmpty()) return; Nan::SetPrivate(object, key, value); } -void LockMasterEnable(const FunctionCallbackInfo& info) { - LockMaster::Enable(); -} - -void LockMasterSetStatus(const FunctionCallbackInfo& info) { - Nan::HandleScope scope; - - // convert the first argument to Status - if(info.Length() >= 0 && info[0]->IsNumber()) { - v8::Local value = Nan::To(info[0]).ToLocalChecked(); - LockMaster::Status status = static_cast(value->Value()); - if(status >= LockMaster::Disabled && status <= LockMaster::Enabled) { - LockMaster::SetStatus(status); - return; - } - } - - // argument error - Nan::ThrowError("Argument must be one 0, 1 or 2"); -} - -void LockMasterGetStatus(const FunctionCallbackInfo& info) { - info.GetReturnValue().Set(Nan::New(LockMaster::GetStatus())); -} - -void LockMasterGetDiagnostics(const FunctionCallbackInfo& info) { - LockMaster::Diagnostics diagnostics(LockMaster::GetDiagnostics()); - - // return a plain JS object with properties - v8::Local result = Nan::New(); - Nan::Set(result, Nan::New("storedMutexesCount").ToLocalChecked(), Nan::New(diagnostics.storedMutexesCount)); - info.GetReturnValue().Set(result); -} - static uv_mutex_t *opensslMutexes; void OpenSSL_LockingCallback(int mode, int type, const char *, int) { @@ -101,8 +69,8 @@ void OpenSSL_ThreadSetup() { // TODO initialize a thread pool per context. Replace uv_default_loop() with node::GetCurrentEventLoop(isolate); ThreadPool libgit2ThreadPool(10, uv_default_loop()); -std::once_flag libraryInitializedFlag; -std::mutex libraryInitializationMutex; +static std::once_flag libraryInitializedFlag; +static std::mutex libraryInitializationMutex; NAN_MODULE_INIT(init) { { @@ -116,6 +84,8 @@ NAN_MODULE_INIT(init) { init_ssh2(); // Initialize libgit2. git_libgit2_init(); + + LockMaster::InitializeGlobal(); }); } @@ -133,19 +103,7 @@ NAN_MODULE_INIT(init) { ConvenientPatch::InitializeComponent(target); GitFilterRegistry::InitializeComponent(target); - NODE_SET_METHOD(target, "enableThreadSafety", LockMasterEnable); - NODE_SET_METHOD(target, "setThreadSafetyStatus", LockMasterSetStatus); - NODE_SET_METHOD(target, "getThreadSafetyStatus", LockMasterGetStatus); - NODE_SET_METHOD(target, "getThreadSafetyDiagnostics", LockMasterGetDiagnostics); - - v8::Local threadSafety = Nan::New(); - Nan::Set(threadSafety, Nan::New("DISABLED").ToLocalChecked(), Nan::New((int)LockMaster::Disabled)); - Nan::Set(threadSafety, Nan::New("ENABLED_FOR_ASYNC_ONLY").ToLocalChecked(), Nan::New((int)LockMaster::EnabledForAsyncOnly)); - Nan::Set(threadSafety, Nan::New("ENABLED").ToLocalChecked(), Nan::New((int)LockMaster::Enabled)); - - Nan::Set(target, Nan::New("THREAD_SAFETY").ToLocalChecked(), threadSafety); - - LockMaster::Initialize(); + LockMaster::InitializeContext(); } NODE_MODULE(nodegit, init) diff --git a/test/runner.js b/test/runner.js index 89732a158..d81a6578f 100644 --- a/test/runner.js +++ b/test/runner.js @@ -5,14 +5,6 @@ var exec = require('../utils/execPromise'); var NodeGit = require('..'); -if(process.env.NODEGIT_TEST_THREADSAFETY) { - console.log('Enabling thread safety in NodeGit'); - NodeGit.enableThreadSafety(); -} else if (process.env.NODEGIT_TEST_THREADSAFETY_ASYNC) { - console.log('Enabling thread safety for async actions only in NodeGit'); - NodeGit.setThreadSafetyStatus(NodeGit.THREAD_SAFETY.ENABLED_FOR_ASYNC_ONLY); -} - var workdirPath = local("repos/workdir"); before(function() { diff --git a/test/tests/thread_safety.js b/test/tests/thread_safety.js deleted file mode 100644 index 257c2d51e..000000000 --- a/test/tests/thread_safety.js +++ /dev/null @@ -1,65 +0,0 @@ -var assert = require("assert"); -var path = require("path"); -var local = path.join.bind(path, __dirname); - -describe("ThreadSafety", function() { - var NodeGit = require("../../"); - var Repository = NodeGit.Repository; - - var reposPath = local("../repos/workdir"); - - beforeEach(function() { - var test = this; - - return Repository.open(reposPath) - .then(function(repo) { - test.repository = repo; - return repo.refreshIndex(); - }) - .then(function(index) { - test.index = index; - }); - }); - - it("can enable and disable thread safety", function() { - var originalValue = NodeGit.getThreadSafetyStatus(); - - NodeGit.enableThreadSafety(); - assert.equal(NodeGit.THREAD_SAFETY.ENABLED, - NodeGit.getThreadSafetyStatus()); - - NodeGit.setThreadSafetyStatus(NodeGit.THREAD_SAFETY.ENABLED_FOR_ASYNC_ONLY); - assert.equal(NodeGit.THREAD_SAFETY.ENABLED_FOR_ASYNC_ONLY, - NodeGit.getThreadSafetyStatus()); - - NodeGit.setThreadSafetyStatus(NodeGit.THREAD_SAFETY.DISABLED); - assert.equal(NodeGit.THREAD_SAFETY.DISABLED, - NodeGit.getThreadSafetyStatus()); - - NodeGit.setThreadSafetyStatus(originalValue); - }); - - it("can lock something and cleanup mutex", function() { - var diagnostics = NodeGit.getThreadSafetyDiagnostics(); - var originalCount = diagnostics.storedMutexesCount; - // call a sync method to guarantee that it stores a mutex, - // and that it will clean up the mutex in a garbage collection cycle - this.repository.headDetached(); - - diagnostics = NodeGit.getThreadSafetyDiagnostics(); - switch(NodeGit.getThreadSafetyStatus()) { - case NodeGit.THREAD_SAFETY.ENABLED: - // this is a fairly vague test - it just tests that something - // had a mutex created for it at some point (i.e., the thread safety - // code is not completely dead) - assert.ok(diagnostics.storedMutexesCount > 0); - break; - case NodeGit.THREAD_SAFETY.ENABLED_FOR_ASYNC_ONLY: - assert.equal(originalCount, diagnostics.storedMutexesCount); - break; - - case NodeGit.THREAD_SAFETY.DISABLED: - assert.equal(0, diagnostics.storedMutexesCount); - } - }); -}); From a820ff48e3d88301a54b52c3ee0cde175d5ac88b Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Wed, 5 Aug 2020 14:41:54 -0700 Subject: [PATCH 05/40] Get rid of libuv in lock_master --- generate/templates/manual/src/lock_master.cc | 97 +++++++++----------- generate/templates/templates/nodegit.cc | 2 - 2 files changed, 43 insertions(+), 56 deletions(-) diff --git a/generate/templates/manual/src/lock_master.cc b/generate/templates/manual/src/lock_master.cc index 553928177..fd1c49192 100644 --- a/generate/templates/manual/src/lock_master.cc +++ b/generate/templates/manual/src/lock_master.cc @@ -1,21 +1,23 @@ #include #include -#include #include #include #include #include +#include +#include +#include #include "../include/lock_master.h" // information about a lockable object // - the mutex used to lock it and the number of outstanding locks struct ObjectInfo { - uv_mutex_t *mutex; + std::shared_ptr mutex; unsigned useCount; - ObjectInfo(uv_mutex_t *mutex, unsigned useCount) - : mutex(mutex), useCount(useCount) + ObjectInfo(unsigned useCount) + : mutex(new std::mutex), useCount(useCount) {} }; @@ -27,17 +29,15 @@ class LockMasterImpl { // A map from objects that are locked (or were locked), to information on their mutex static std::map mutexes; // A mutex used for the mutexes map - static uv_mutex_t mapMutex; + static std::mutex mapMutex; - // A libuv key used to store the current thread-specific LockMasterImpl instance - static uv_key_t currentLockMasterKey; + // A thread local storage slot for the current thread-specific LockMasterImpl instance + thread_local static LockMasterImpl* currentLockMaster; // Cleans up any mutexes that are not currently used static NAN_GC_CALLBACK(CleanupMutexes); public: - static void InitializeGlobal(); - static void InitializeContext(); // INSTANCE variables / methods @@ -47,13 +47,13 @@ class LockMasterImpl { std::set objectsToLock; // Mutexes locked by this LockMaster on construction and unlocked on destruction - std::vector GetMutexes(int useCountDelta); + std::vector> GetMutexes(int useCountDelta); void Register(); void Unregister(); public: static LockMasterImpl *CurrentLockMasterImpl() { - return (LockMasterImpl *)uv_key_get(¤tLockMasterKey); + return (LockMasterImpl *)currentLockMaster; } LockMasterImpl() { @@ -74,30 +74,22 @@ class LockMasterImpl { }; std::map LockMasterImpl::mutexes; -uv_mutex_t LockMasterImpl::mapMutex; -uv_key_t LockMasterImpl::currentLockMasterKey; - -void LockMasterImpl::InitializeGlobal() { - uv_mutex_init(&mapMutex); - uv_key_create(¤tLockMasterKey); -} +std::mutex LockMasterImpl::mapMutex; +thread_local LockMasterImpl* LockMasterImpl::currentLockMaster = NULL; void LockMasterImpl::InitializeContext() { Nan::AddGCEpilogueCallback(CleanupMutexes); } NAN_GC_CALLBACK(LockMasterImpl::CleanupMutexes) { - uv_mutex_lock(&mapMutex); + std::lock_guard lock(mapMutex); for (auto it = mutexes.begin(); it != mutexes.end(); ) { - uv_mutex_t *mutex = it->second.mutex; - unsigned useCount = it->second.useCount; // if the mutex is not used by any LockMasters, // we can destroy it + unsigned useCount = it->second.useCount; if (!useCount) { - uv_mutex_destroy(mutex); - free(mutex); auto to_erase = it; it++; mutexes.erase(to_erase); @@ -105,35 +97,27 @@ NAN_GC_CALLBACK(LockMasterImpl::CleanupMutexes) { it++; } } - - uv_mutex_unlock(&mapMutex); -} - -void LockMaster::InitializeGlobal() { - LockMasterImpl::InitializeGlobal(); } void LockMaster::InitializeContext() { LockMasterImpl::InitializeContext(); } -std::vector LockMasterImpl::GetMutexes(int useCountDelta) { - std::vector objectMutexes; - - uv_mutex_lock(&mapMutex); +std::vector> LockMasterImpl::GetMutexes(int useCountDelta) { + std::vector> objectMutexes; + std::lock_guard lock(mapMutex); for (auto object : objectsToLock) { - if(object) { + if (object) { // ensure we have an initialized mutex for each object auto mutexIt = mutexes.find(object); - if(mutexIt == mutexes.end()) { + if (mutexIt == mutexes.end()) { mutexIt = mutexes.insert( std::make_pair( object, - ObjectInfo((uv_mutex_t *)malloc(sizeof(uv_mutex_t)), 0U) + ObjectInfo(0U) ) ).first; - uv_mutex_init(mutexIt->second.mutex); } objectMutexes.push_back(mutexIt->second.mutex); @@ -141,61 +125,66 @@ std::vector LockMasterImpl::GetMutexes(int useCountDelta) { } } - uv_mutex_unlock(&mapMutex); - return objectMutexes; } void LockMasterImpl::Register() { - uv_key_set(¤tLockMasterKey, this); + currentLockMaster = this; } void LockMasterImpl::Unregister() { - uv_key_set(¤tLockMasterKey, NULL); + currentLockMaster = NULL; } void LockMasterImpl::Lock(bool acquireMutexes) { - std::vector objectMutexes = GetMutexes(acquireMutexes * 1); + std::vector> objectMutexes = GetMutexes(acquireMutexes * 1); auto alreadyLocked = objectMutexes.end(); + std::vector>::iterator it; // we will attempt to lock all the mutexes at the same time to avoid deadlocks // note in most cases we are locking 0 or 1 mutexes. more than 1 implies // passing objects with different repos/owners in the same call. - std::vector::iterator it; do { // go through all the mutexes and try to lock them - for(it = objectMutexes.begin(); it != objectMutexes.end(); it++) { - // if we already locked this mutex in a previous pass via uv_mutex_lock, + for (it = objectMutexes.begin(); it != objectMutexes.end(); it++) { + // if we already locked this mutex in a previous pass via std::mutex::lock, // we don't need to lock it again if (it == alreadyLocked) { continue; } + // first, try to lock (non-blocking) - bool failure = uv_mutex_trylock(*it); - if(failure) { + bool success = (*it)->try_lock(); + if (!success) { // we have failed to lock a mutex... unlock everything we have locked - std::for_each(objectMutexes.begin(), it, uv_mutex_unlock); + std::for_each(objectMutexes.begin(), it, [](std::shared_ptr mutex) { + mutex->unlock(); + }); + if (alreadyLocked > it && alreadyLocked != objectMutexes.end()) { - uv_mutex_unlock(*alreadyLocked); + (*alreadyLocked)->unlock(); } + // now do a blocking lock on what we couldn't lock - uv_mutex_lock(*it); + (*it)->lock(); // mark that we have already locked this one // if there are more mutexes than this one, we will go back to locking everything alreadyLocked = it; break; } } - } while(it != objectMutexes.end()); + } while (it != objectMutexes.end()); } void LockMasterImpl::Unlock(bool releaseMutexes) { // Get the mutexes but don't decrement their use count until after we've // unlocked them all. - std::vector objectMutexes = GetMutexes(0); + std::vector> objectMutexes = GetMutexes(0); - std::for_each(objectMutexes.begin(), objectMutexes.end(), uv_mutex_unlock); + std::for_each(objectMutexes.begin(), objectMutexes.end(), [](std::shared_ptr mutex) { + mutex->unlock(); + }); GetMutexes(releaseMutexes * -1); } @@ -222,7 +211,7 @@ void LockMaster::ObjectsToLockAdded() { void LockMaster::TemporaryUnlock::ConstructorImpl() { impl = LockMasterImpl::CurrentLockMasterImpl(); - if(impl) { + if (impl) { impl->Unlock(false); } } diff --git a/generate/templates/templates/nodegit.cc b/generate/templates/templates/nodegit.cc index 3d742adb6..5d16fa3bb 100644 --- a/generate/templates/templates/nodegit.cc +++ b/generate/templates/templates/nodegit.cc @@ -84,8 +84,6 @@ NAN_MODULE_INIT(init) { init_ssh2(); // Initialize libgit2. git_libgit2_init(); - - LockMaster::InitializeGlobal(); }); } From 67d77d917d87341f95f67d46a8157dd417e38061 Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Mon, 17 Aug 2020 16:59:42 -0700 Subject: [PATCH 06/40] Get rid of unnecessary libuv in thread pool --- generate/templates/manual/include/semaphore.h | 20 +++++ .../templates/manual/include/thread_pool.h | 29 +++++-- generate/templates/manual/src/semaphore.cc | 17 +++++ generate/templates/manual/src/thread_pool.cc | 75 +++++++++---------- generate/templates/templates/binding.gyp | 1 + generate/templates/templates/nodegit.js | 2 +- 6 files changed, 96 insertions(+), 48 deletions(-) create mode 100644 generate/templates/manual/include/semaphore.h create mode 100644 generate/templates/manual/src/semaphore.cc diff --git a/generate/templates/manual/include/semaphore.h b/generate/templates/manual/include/semaphore.h new file mode 100644 index 000000000..02f1df446 --- /dev/null +++ b/generate/templates/manual/include/semaphore.h @@ -0,0 +1,20 @@ +#ifndef NODEGIT_SEMAPHORE +#define NODEGIT_SEMAPHORE + +#include +#include + +class Semaphore { +public: + Semaphore(); + + void post(); + void wait(); + +private: + std::mutex mutex; + std::condition_variable condition; + unsigned long count; +}; + +#endif diff --git a/generate/templates/manual/include/thread_pool.h b/generate/templates/manual/include/thread_pool.h index 8a346028d..12a3ebe3c 100644 --- a/generate/templates/manual/include/thread_pool.h +++ b/generate/templates/manual/include/thread_pool.h @@ -2,7 +2,11 @@ #define THREAD_POOL_H #include +#include #include +#include + +#include "./semaphore.h" class ThreadPool { public: @@ -15,8 +19,12 @@ class ThreadPool { void *data; Work(Callback workCallback, Callback completionCallback, void *data) - : workCallback(workCallback), completionCallback(completionCallback), data(data) { - } + : workCallback(workCallback), completionCallback(completionCallback), data(data) + {} + + Work() + : workCallback(NULL), completionCallback(NULL), data(NULL) + {} }; struct LoopCallback { @@ -25,22 +33,27 @@ class ThreadPool { bool isWork; LoopCallback(Callback callback, void *data, bool isWork) - : callback(callback), data(data), isWork(isWork) { - } + : callback(callback), data(data), isWork(isWork) + {} + + LoopCallback() + : callback(NULL), data(NULL), isWork(false) + {} }; // work to be performed on the threadpool std::queue workQueue; - uv_mutex_t workMutex; - uv_sem_t workSemaphore; + std::mutex workMutex; + Semaphore workSemaphore; int workInProgressCount; // completion and async callbacks to be performed on the loop std::queue loopQueue; - uv_mutex_t loopMutex; + std::mutex loopMutex; uv_async_t loopAsync; - static void RunEventQueue(void *threadPool); + std::vector threads; + void RunEventQueue(); static void RunLoopCallbacks(uv_async_t* handle); void RunLoopCallbacks(); diff --git a/generate/templates/manual/src/semaphore.cc b/generate/templates/manual/src/semaphore.cc new file mode 100644 index 000000000..8185ba774 --- /dev/null +++ b/generate/templates/manual/src/semaphore.cc @@ -0,0 +1,17 @@ +#include "../include/semaphore.h" + +Semaphore::Semaphore() + : count(0) +{} + +void Semaphore::post() { + std::lock_guard lock(mutex); + ++count; + condition.notify_one(); +} + +void Semaphore::wait() { + std::unique_lock lock(mutex); + while (!count) condition.wait(lock); + --count; +} diff --git a/generate/templates/manual/src/thread_pool.cc b/generate/templates/manual/src/thread_pool.cc index ef7a4528d..c7a716e7f 100644 --- a/generate/templates/manual/src/thread_pool.cc +++ b/generate/templates/manual/src/thread_pool.cc @@ -1,37 +1,35 @@ #include #include "../include/thread_pool.h" -ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop) { - uv_mutex_init(&workMutex); - uv_sem_init(&workSemaphore, 0); - +ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop) +{ uv_async_init(loop, &loopAsync, RunLoopCallbacks); loopAsync.data = this; uv_unref((uv_handle_t *)&loopAsync); - uv_mutex_init(&loopMutex); workInProgressCount = 0; - for(int i=0; i lock(workMutex); + // there is work on the thread pool - reference the handle so + // node doesn't terminate + uv_ref((uv_handle_t *)&loopAsync); + workQueue.push(Work(workCallback, completionCallback, data)); + workInProgressCount++; + } + workSemaphore.post(); } void ThreadPool::QueueLoopCallback(Callback callback, void *data, bool isWork) { // push the callback into the queue - uv_mutex_lock(&loopMutex); + std::lock_guard lock(loopMutex); LoopCallback loopCallback(callback, data, isWork); bool queueWasEmpty = loopQueue.empty(); loopQueue.push(loopCallback); @@ -40,26 +38,23 @@ void ThreadPool::QueueLoopCallback(Callback callback, void *data, bool isWork) { if (queueWasEmpty) { uv_async_send(&loopAsync); } - uv_mutex_unlock(&loopMutex); } void ThreadPool::ExecuteReverseCallback(Callback reverseCallback, void *data) { QueueLoopCallback(reverseCallback, data, false); } -void ThreadPool::RunEventQueue(void *threadPool) { - static_cast(threadPool)->RunEventQueue(); -} - void ThreadPool::RunEventQueue() { for ( ; ; ) { // wait until there is work to do - uv_sem_wait(&workSemaphore); - uv_mutex_lock(&workMutex); - // the semaphore should guarantee that queue is not empty - Work work = workQueue.front(); - workQueue.pop(); - uv_mutex_unlock(&workMutex); + workSemaphore.wait(); + Work work; + { + std::lock_guard lock(workMutex); + // the semaphore should guarantee that queue is not empty + work = workQueue.front(); + workQueue.pop(); + } // perform the queued work (*work.workCallback)(work.data); @@ -77,30 +72,32 @@ void ThreadPool::RunLoopCallbacks() { Nan::HandleScope scope; v8::Local context = Nan::GetCurrentContext(); node::CallbackScope callbackScope(context->GetIsolate(), Nan::New(), {0, 0}); - // get the next callback to run - uv_mutex_lock(&loopMutex); - LoopCallback loopCallback = loopQueue.front(); - uv_mutex_unlock(&loopMutex); + LoopCallback loopCallback; + { + std::lock_guard lock(loopMutex); + // get the next callback to run + loopCallback = loopQueue.front(); + } // perform the queued loop callback (*loopCallback.callback)(loopCallback.data); // pop the queue, and if necessary, re-trigger RunLoopCallbacks - uv_mutex_lock(&loopMutex); - loopQueue.pop(); - if (!loopQueue.empty()) { - uv_async_send(&loopAsync); + { + std::lock_guard lock(loopMutex); + loopQueue.pop(); + if (!loopQueue.empty()) { + uv_async_send(&loopAsync); + } } - uv_mutex_unlock(&loopMutex); // if there is no ongoing work / completion processing, node doesn't need // to be prevented from terminating if (loopCallback.isWork) { - uv_mutex_lock(&workMutex); + std::lock_guard lock(workMutex); workInProgressCount --; if(!workInProgressCount) { uv_unref((uv_handle_t *)&loopAsync); } - uv_mutex_unlock(&workMutex); } } diff --git a/generate/templates/templates/binding.gyp b/generate/templates/templates/binding.gyp index 1b23ed2fd..52620e4bb 100644 --- a/generate/templates/templates/binding.gyp +++ b/generate/templates/templates/binding.gyp @@ -59,6 +59,7 @@ "src/convenient_hunk.cc", "src/filter_registry.cc", "src/git_buf_converter.cc", + "src/semaphore.cc", "src/str_array_converter.cc", "src/thread_pool.cc", {% each %} diff --git a/generate/templates/templates/nodegit.js b/generate/templates/templates/nodegit.js index a580b4df7..00c8ca992 100644 --- a/generate/templates/templates/nodegit.js +++ b/generate/templates/templates/nodegit.js @@ -5,7 +5,7 @@ var worker = require("worker_threads"); var rawApi; if (!worker.isMainThread || typeof importScripts === "function") { - throw new Error("NodeGit is currently not safe to run in a worker thread or web worker"); + throw new Error("NodeGit is currently not safe to run in a worker thread or web worker"); // jshint ignore:line } // Attempt to load the production release first, if it fails fall back to the From 88711bf02855e85466935e11b1497fdff479b2f6 Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Mon, 17 Aug 2020 17:11:46 -0700 Subject: [PATCH 07/40] Fix jshint --- generate/templates/templates/nodegit.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/generate/templates/templates/nodegit.js b/generate/templates/templates/nodegit.js index 00c8ca992..8009150ef 100644 --- a/generate/templates/templates/nodegit.js +++ b/generate/templates/templates/nodegit.js @@ -1,10 +1,14 @@ var _ = require("lodash"); var util = require("util"); -var worker = require("worker_threads"); +var worker; + +try { + worker = require("worker_threads"); +} catch (e) {} var rawApi; -if (!worker.isMainThread || typeof importScripts === "function") { +if (worker && (!worker.isMainThread || typeof importScripts === "function")) { throw new Error("NodeGit is currently not safe to run in a worker thread or web worker"); // jshint ignore:line } From deb1430acaba85531fe284315e799c1a40bca0fd Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Tue, 25 Aug 2020 14:15:57 -0700 Subject: [PATCH 08/40] Rewrite thread pool for context awareness --- generate/templates/manual/clone/clone.cc | 46 +- .../manual/commit/extract_signature.cc | 33 +- generate/templates/manual/filter_list/load.cc | 31 +- .../templates/manual/filter_source/repo.cc | 24 +- .../templates/manual/include/async_baton.h | 142 ++-- .../include/async_libgit2_queue_worker.h | 33 - .../templates/manual/include/async_worker.h | 18 + generate/templates/manual/include/context.h | 43 ++ .../manual/include/convenient_hunk.h | 10 +- .../manual/include/convenient_patch.h | 10 +- .../manual/include/filter_registry.h | 23 +- .../templates/manual/include/lock_master.h | 279 ++++---- generate/templates/manual/include/nodegit.h | 4 - .../manual/include/nodegit_wrapper.h | 6 +- .../manual/include/promise_completion.h | 18 +- generate/templates/manual/include/semaphore.h | 20 - .../templates/manual/include/thread_pool.h | 93 +-- generate/templates/manual/include/wrapper.h | 5 +- .../manual/patches/convenient_patches.cc | 71 +- generate/templates/manual/remote/ls.cc | 49 +- .../manual/repository/get_references.cc | 9 +- .../manual/repository/get_remotes.cc | 10 +- .../manual/repository/get_submodules.cc | 10 +- .../manual/repository/refresh_references.cc | 10 +- .../templates/manual/revwalk/commit_walk.cc | 8 +- .../templates/manual/revwalk/fast_walk.cc | 8 +- .../manual/revwalk/file_history_walk.cc | 8 +- generate/templates/manual/src/async_baton.cc | 68 +- generate/templates/manual/src/async_worker.cc | 11 + generate/templates/manual/src/context.cc | 42 ++ .../templates/manual/src/convenient_hunk.cc | 45 +- .../templates/manual/src/convenient_patch.cc | 63 +- .../templates/manual/src/filter_registry.cc | 42 +- generate/templates/manual/src/lock_master.cc | 357 +++++----- .../templates/manual/src/nodegit_wrapper.cc | 13 +- .../manual/src/promise_completion.cc | 47 +- .../templates/manual/src/reference_counter.cc | 4 +- generate/templates/manual/src/semaphore.cc | 17 - generate/templates/manual/src/thread_pool.cc | 628 +++++++++++++++--- generate/templates/manual/src/wrapper.cc | 20 +- generate/templates/partials/async_function.cc | 31 +- .../templates/partials/callback_helpers.cc | 6 +- .../templates/partials/field_accessors.cc | 13 +- generate/templates/partials/sync_function.cc | 2 +- generate/templates/partials/traits.h | 1 + generate/templates/templates/binding.gyp | 9 +- generate/templates/templates/class_content.cc | 25 +- generate/templates/templates/class_header.h | 27 +- generate/templates/templates/nodegit.cc | 24 +- .../templates/templates/struct_content.cc | 13 +- generate/templates/templates/struct_header.h | 16 +- 51 files changed, 1607 insertions(+), 938 deletions(-) delete mode 100644 generate/templates/manual/include/async_libgit2_queue_worker.h create mode 100644 generate/templates/manual/include/async_worker.h create mode 100644 generate/templates/manual/include/context.h delete mode 100644 generate/templates/manual/include/semaphore.h create mode 100644 generate/templates/manual/src/async_worker.cc create mode 100644 generate/templates/manual/src/context.cc delete mode 100644 generate/templates/manual/src/semaphore.cc diff --git a/generate/templates/manual/clone/clone.cc b/generate/templates/manual/clone/clone.cc index a7ac262dc..9541f05f3 100644 --- a/generate/templates/manual/clone/clone.cc +++ b/generate/templates/manual/clone/clone.cc @@ -86,36 +86,42 @@ NAN_METHOD(GitClone::Clone) { if (!info[2]->IsUndefined() && !info[2]->IsNull()) worker->SaveToPersistent("options", Nan::To(info[2]).ToLocalChecked()); - AsyncLibgit2QueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitClone::CloneWorker::AcquireLocks() { + nodegit::LockMaster lockMaster( + true, + baton->url, + baton->local_path, + baton->options + ); + return lockMaster; +} + void GitClone::CloneWorker::Execute() { git_error_clear(); - { - LockMaster lockMaster( - /*asyncAction: */ true, baton->url, baton->local_path, baton->options); - - git_repository *repo; - int result = - git_clone(&repo, baton->url, baton->local_path, baton->options); + git_repository *repo; + int result = + git_clone(&repo, baton->url, baton->local_path, baton->options); - if (result == GIT_OK) { - // This is required to clean up after the clone to avoid file locking - // issues in Windows and potentially other issues we don't know about. - git_repository_free(repo); + if (result == GIT_OK) { + // This is required to clean up after the clone to avoid file locking + // issues in Windows and potentially other issues we don't know about. + git_repository_free(repo); - // We want to provide a valid repository object, so reopen the repository - // after clone and cleanup. - result = git_repository_open(&baton->out, baton->local_path); - } + // We want to provide a valid repository object, so reopen the repository + // after clone and cleanup. + result = git_repository_open(&baton->out, baton->local_path); + } - baton->error_code = result; + baton->error_code = result; - if (result != GIT_OK && git_error_last() != NULL) { - baton->error = git_error_dup(git_error_last()); - } + if (result != GIT_OK && git_error_last() != NULL) { + baton->error = git_error_dup(git_error_last()); } } diff --git a/generate/templates/manual/commit/extract_signature.cc b/generate/templates/manual/commit/extract_signature.cc index e96d0cc7f..5c66cbe3f 100644 --- a/generate/templates/manual/commit/extract_signature.cc +++ b/generate/templates/manual/commit/extract_signature.cc @@ -67,31 +67,30 @@ NAN_METHOD(GitCommit::ExtractSignature) ExtractSignatureWorker *worker = new ExtractSignatureWorker(baton, callback); worker->SaveToPersistent("repo", Nan::To(info[0]).ToLocalChecked()); worker->SaveToPersistent("commit_id", Nan::To(info[1]).ToLocalChecked()); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitCommit::ExtractSignatureWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true, baton->repo); + return lockMaster; +} + void GitCommit::ExtractSignatureWorker::Execute() { git_error_clear(); - { - LockMaster lockMaster( - /*asyncAction: */true, - baton->repo - ); + baton->error_code = git_commit_extract_signature( + &baton->signature, + &baton->signed_data, + baton->repo, + baton->commit_id, + (const char *)baton->field + ); - baton->error_code = git_commit_extract_signature( - &baton->signature, - &baton->signed_data, - baton->repo, - baton->commit_id, - (const char *)baton->field - ); - - if (baton->error_code != GIT_OK && git_error_last() != NULL) { - baton->error = git_error_dup(git_error_last()); - } + if (baton->error_code != GIT_OK && git_error_last() != NULL) { + baton->error = git_error_dup(git_error_last()); } } diff --git a/generate/templates/manual/filter_list/load.cc b/generate/templates/manual/filter_list/load.cc index fd02a44e6..0107dfb81 100644 --- a/generate/templates/manual/filter_list/load.cc +++ b/generate/templates/manual/filter_list/load.cc @@ -103,25 +103,31 @@ NAN_METHOD(GitFilterList::Load) { if (!info[4]->IsUndefined() && !info[4]->IsNull()) worker->SaveToPersistent("flags", Nan::To(info[4]).ToLocalChecked()); - AsyncLibgit2QueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitFilterList::LoadWorker::AcquireLocks() { + nodegit::LockMaster lockMaster( + true, + baton->repo, + baton->blob, + baton->path + ); + return lockMaster; +} + void GitFilterList::LoadWorker::Execute() { git_error_clear(); - { - LockMaster lockMaster( - /*asyncAction: */ true, baton->repo, baton->blob, baton->path); + int result = git_filter_list_load(&baton->filters, baton->repo, baton->blob, + baton->path, baton->mode, baton->flags); - int result = git_filter_list_load(&baton->filters, baton->repo, baton->blob, - baton->path, baton->mode, baton->flags); + baton->error_code = result; - baton->error_code = result; - - if (result != GIT_OK && git_error_last() != NULL) { - baton->error = git_error_dup(git_error_last()); - } + if (result != GIT_OK && git_error_last() != NULL) { + baton->error = git_error_dup(git_error_last()); } } @@ -133,7 +139,8 @@ void GitFilterList::LoadWorker::HandleOKCallback() { if (baton->filters != NULL) { // GitFilterList baton->filters v8::Local owners = Nan::New(0); - v8::Local filterRegistry = Nan::New(GitFilterRegistry::persistentHandle); + nodegit::Context *nodegitContext = nodegit::Context::GetCurrentContext(); + v8::Local filterRegistry = nodegitContext->GetFromPersistent("FilterRegistry").As(); v8::Local propertyNames = Nan::GetPropertyNames(filterRegistry).ToLocalChecked(); Nan::Set( diff --git a/generate/templates/manual/filter_source/repo.cc b/generate/templates/manual/filter_source/repo.cc index 57c2a07f7..96d2d5e0b 100644 --- a/generate/templates/manual/filter_source/repo.cc +++ b/generate/templates/manual/filter_source/repo.cc @@ -23,24 +23,26 @@ NAN_METHOD(GitFilterSource::Repo) { worker->SaveToPersistent("src", info.This()); - AsyncLibgit2QueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitFilterSource::RepoWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true, baton->src); + return lockMaster; +} + void GitFilterSource::RepoWorker::Execute() { git_error_clear(); - { - LockMaster lockMaster(true, baton->src); - - git_repository *repo = git_filter_source_repo(baton->src); - baton->error_code = git_repository_open(&repo, git_repository_path(repo)); + git_repository *repo = git_filter_source_repo(baton->src); + baton->error_code = git_repository_open(&repo, git_repository_path(repo)); - if (baton->error_code == GIT_OK) { - baton->out = repo; - } else if (git_error_last() != NULL) { - baton->error = git_error_dup(git_error_last()); - } + if (baton->error_code == GIT_OK) { + baton->out = repo; + } else if (git_error_last() != NULL) { + baton->error = git_error_dup(git_error_last()); } } diff --git a/generate/templates/manual/include/async_baton.h b/generate/templates/manual/include/async_baton.h index f8373cd0d..b9df1cfec 100644 --- a/generate/templates/manual/include/async_baton.h +++ b/generate/templates/manual/include/async_baton.h @@ -1,97 +1,71 @@ #ifndef ASYNC_BATON #define ASYNC_BATON -#include +#include +#include +#include #include #include "lock_master.h" #include "nodegit.h" +#include "thread_pool.h" -// Base class for Batons used for callbacks (for example, -// JS functions passed as callback parameters, -// or field properties of configuration objects whose values are callbacks) -struct AsyncBaton { - uv_sem_t semaphore; - - virtual ~AsyncBaton() {} -}; - -void deleteBaton(AsyncBaton *baton); - -template -struct AsyncBatonWithResult : public AsyncBaton { - ResultT result; - ResultT defaultResult; // result returned if the callback doesn't return anything valid - void (*onCompletion)(AsyncBaton *); - - AsyncBatonWithResult(const ResultT &defaultResult) - : defaultResult(defaultResult) { - } - - void Done() { - if (onCompletion) { - onCompletion(this); - } else { - // signal completion - uv_sem_post(&semaphore); - } - } - - ResultT ExecuteAsync(ThreadPool::Callback asyncCallback, void (*onCompletion)(AsyncBaton *) = NULL) { - result = 0; - this->onCompletion = onCompletion; - if (!onCompletion) { - uv_sem_init(&semaphore, 0); - } - - { - LockMaster::TemporaryUnlock temporaryUnlock; - - libgit2ThreadPool.ExecuteReverseCallback(asyncCallback, this); - - if (!onCompletion) { - // wait for completion - uv_sem_wait(&semaphore); - uv_sem_destroy(&semaphore); +namespace nodegit { + // Base class for Batons used for callbacks (for example, + // JS functions passed as callback parameters, + // or field properties of configuration objects whose values are callbacks) + class AsyncBaton { + public: + typedef std::function AsyncCallback; + typedef std::function CompletionCallback; + + AsyncBaton(); + + virtual ~AsyncBaton() {} + + void Done(); + + Nan::AsyncResource *GetAsyncResource(); + + protected: + void ExecuteAsyncPerform(AsyncCallback asyncCallback, CompletionCallback onCompletion); + + private: + void SignalCompletion(); + void WaitForCompletion(); + + Nan::AsyncResource *asyncResource; + ThreadPool::Callback onCompletion; + std::unique_ptr completedMutex; + std::condition_variable completedCondition; + bool hasCompleted; + }; + + void deleteBaton(AsyncBaton *baton); + + template + class AsyncBatonWithResult : public AsyncBaton { + public: + ResultT defaultResult; // result returned if the callback doesn't return anything valid + ResultT result; + + AsyncBatonWithResult(const ResultT &defaultResult) + : defaultResult(defaultResult) { } - } - - return result; - } -}; - -struct AsyncBatonWithNoResult : public AsyncBaton { - void (*onCompletion)(AsyncBaton *); - - void Done() { - if (onCompletion) { - onCompletion(this); - } else { - // signal completion - uv_sem_post(&semaphore); - } - } - - void ExecuteAsync(ThreadPool::Callback asyncCallback, void (*onCompletion)(AsyncBaton *) = NULL) { - this->onCompletion = onCompletion; - if (!onCompletion) { - uv_sem_init(&semaphore, 0); - } - - { - LockMaster::TemporaryUnlock temporaryUnlock; - - libgit2ThreadPool.ExecuteReverseCallback(asyncCallback, this); - - if (!onCompletion) { - // wait for completion - uv_sem_wait(&semaphore); - uv_sem_destroy(&semaphore); + + ResultT ExecuteAsync(AsyncBaton::AsyncCallback asyncCallback, AsyncBaton::CompletionCallback onCompletion = nullptr) { + result = 0; + ExecuteAsyncPerform(asyncCallback, onCompletion); + return result; } - } + }; - return; - } -}; + class AsyncBatonWithNoResult : public AsyncBaton { + public: + void ExecuteAsync(AsyncBaton::AsyncCallback asyncCallback, AsyncBaton::CompletionCallback onCompletion = nullptr) { + ExecuteAsyncPerform(asyncCallback, onCompletion); + } + }; +} #endif diff --git a/generate/templates/manual/include/async_libgit2_queue_worker.h b/generate/templates/manual/include/async_libgit2_queue_worker.h deleted file mode 100644 index f3ddf2fb3..000000000 --- a/generate/templates/manual/include/async_libgit2_queue_worker.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ASYNC_LIBGIT2_QUEUE_WORKER_H -#define ASYNC_LIBGIT2_QUEUE_WORKER_H - -#include -#include -#include "../include/thread_pool.h" -#include "../include/nodegit.h" - - -// Runs WorkComplete of the scheduled AsyncWorker, -// and destroys it. This is run in the uv_default_loop event loop. -NAN_INLINE void AsyncLibgit2Complete (void* data) { - Nan::AsyncWorker *worker = static_cast(data); - worker->WorkComplete(); - worker->Destroy(); -} - -// Runs Execute of the scheduled AyncWorker on the dedicated libgit2 thread / -// event loop, and schedules the WorkComplete callback to run on the -// uv_default_loop event loop -NAN_INLINE void AsyncLibgit2Execute (void *vworker) { - // execute the worker - Nan::AsyncWorker *worker = static_cast(vworker); - worker->Execute(); -} - -// Schedules the AsyncWorker to run on the dedicated libgit2 thread / event loop, -// and on completion AsyncLibgit2Complete on the default loop -NAN_INLINE void AsyncLibgit2QueueWorker (Nan::AsyncWorker* worker) { - libgit2ThreadPool.QueueWork(AsyncLibgit2Execute, AsyncLibgit2Complete, worker); -} - -#endif diff --git a/generate/templates/manual/include/async_worker.h b/generate/templates/manual/include/async_worker.h new file mode 100644 index 000000000..fc10893e6 --- /dev/null +++ b/generate/templates/manual/include/async_worker.h @@ -0,0 +1,18 @@ +#ifndef NODEGIT_ASYNC_WORKER +#define NODEGIT_ASYNC_WORKER + +#include +#include "lock_master.h" + +namespace nodegit { + class AsyncWorker : public Nan::AsyncWorker { + public: + AsyncWorker(Nan::Callback *callback, const char *resourceName); + + virtual nodegit::LockMaster AcquireLocks() = 0; + + Nan::AsyncResource *GetAsyncResource(); + }; +} + +#endif diff --git a/generate/templates/manual/include/context.h b/generate/templates/manual/include/context.h new file mode 100644 index 000000000..8b2ef66be --- /dev/null +++ b/generate/templates/manual/include/context.h @@ -0,0 +1,43 @@ +#ifndef NODEGIT_CONTEXT +#define NODEGIT_CONTEXT + +#include +#include +#include +#include +#include + +#include "async_worker.h" +#include "thread_pool.h" + +namespace nodegit { + class Context { + public: + Context(v8::Isolate *isolate); + + void QueueWorker(nodegit::AsyncWorker *worker); + + void SaveToPersistent(std::string key, const v8::Local &value); + + v8::Local GetFromPersistent(std::string key); + + static Context *GetCurrentContext(); + + ~Context(); + + private: + v8::Isolate *isolate; + + ThreadPool threadPool; + + // This map contains persistent handles that need to be cleaned up + // after the context has been torn down. + // Often this is used as a context-aware storage cell for `*::InitializeComponent` + // to store function templates on them. + Nan::Persistent persistentStorage; + + static std::map contexts; + }; +} + +#endif diff --git a/generate/templates/manual/include/convenient_hunk.h b/generate/templates/manual/include/convenient_hunk.h index 37e9ab111..3953a4680 100644 --- a/generate/templates/manual/include/convenient_hunk.h +++ b/generate/templates/manual/include/convenient_hunk.h @@ -5,6 +5,8 @@ #include #include "async_baton.h" +#include "async_worker.h" +#include "lock_master.h" #include "promise_completion.h" extern "C" { @@ -26,8 +28,7 @@ using namespace v8; class ConvenientHunk : public Nan::ObjectWrap { public: - static Nan::Persistent constructor_template; - static void InitializeComponent (v8::Local target); + static void InitializeComponent (v8::Local target, nodegit::Context *nodegitContext); static v8::Local New(void *raw); @@ -55,16 +56,17 @@ class ConvenientHunk : public Nan::ObjectWrap { HunkData *hunk; std::vector *lines; }; - class LinesWorker : public Nan::AsyncWorker { + class LinesWorker : public nodegit::AsyncWorker { public: LinesWorker( LinesBaton *_baton, Nan::Callback *callback - ) : Nan::AsyncWorker(callback) + ) : nodegit::AsyncWorker(callback, "nodegit:AsyncWorker:ConvenientHunk:Lines") , baton(_baton) {}; ~LinesWorker() {}; void Execute(); void HandleOKCallback(); + nodegit::LockMaster AcquireLocks(); private: LinesBaton *baton; diff --git a/generate/templates/manual/include/convenient_patch.h b/generate/templates/manual/include/convenient_patch.h index 9d6921ef8..ea8dcf95d 100644 --- a/generate/templates/manual/include/convenient_patch.h +++ b/generate/templates/manual/include/convenient_patch.h @@ -5,6 +5,8 @@ #include #include "async_baton.h" +#include "async_worker.h" +#include "lock_master.h" #include "promise_completion.h" extern "C" { @@ -37,8 +39,7 @@ using namespace v8; class ConvenientPatch : public Nan::ObjectWrap { public: - static Nan::Persistent constructor_template; - static void InitializeComponent (v8::Local target); + static void InitializeComponent (v8::Local target, nodegit::Context *nodegitContext); static v8::Local New(void *raw); @@ -67,16 +68,17 @@ class ConvenientPatch : public Nan::ObjectWrap { PatchData *patch; std::vector *hunks; }; - class HunksWorker : public Nan::AsyncWorker { + class HunksWorker : public nodegit::AsyncWorker { public: HunksWorker( HunksBaton *_baton, Nan::Callback *callback - ) : Nan::AsyncWorker(callback) + ) : nodegit::AsyncWorker(callback, "nodegit:AsyncWorker:ConvenientPatch:Hunks") , baton(_baton) {}; ~HunksWorker() {}; void Execute(); void HandleOKCallback(); + nodegit::LockMaster AcquireLocks(); private: HunksBaton *baton; diff --git a/generate/templates/manual/include/filter_registry.h b/generate/templates/manual/include/filter_registry.h index b75938218..cefcbcaed 100644 --- a/generate/templates/manual/include/filter_registry.h +++ b/generate/templates/manual/include/filter_registry.h @@ -6,6 +6,9 @@ #include #include "async_baton.h" +#include "async_worker.h" +#include "context.h" +#include "lock_master.h" #include "nodegit_wrapper.h" #include "promise_completion.h" @@ -23,12 +26,10 @@ using namespace v8; class GitFilterRegistry : public Nan::ObjectWrap { public: - static void InitializeComponent(v8::Local target); - - static Nan::Persistent persistentHandle; + static void InitializeComponent(v8::Local target, nodegit::Context *nodegitContext); private: - + static NAN_METHOD(GitFilterRegister); static NAN_METHOD(GitFilterUnregister); @@ -47,25 +48,27 @@ class GitFilterRegistry : public Nan::ObjectWrap { int error_code; }; - class RegisterWorker : public Nan::AsyncWorker { + class RegisterWorker : public nodegit::AsyncWorker { public: - RegisterWorker(FilterRegisterBaton *_baton, Nan::Callback *callback) - : Nan::AsyncWorker(callback), baton(_baton) {}; + RegisterWorker(FilterRegisterBaton *_baton, Nan::Callback *callback) + : nodegit::AsyncWorker(callback, "nodegit:AsyncWorker:FilterRegistry:Register"), baton(_baton) {}; ~RegisterWorker() {}; void Execute(); void HandleOKCallback(); + nodegit::LockMaster AcquireLocks(); private: FilterRegisterBaton *baton; }; - class UnregisterWorker : public Nan::AsyncWorker { + class UnregisterWorker : public nodegit::AsyncWorker { public: - UnregisterWorker(FilterUnregisterBaton *_baton, Nan::Callback *callback) - : Nan::AsyncWorker(callback), baton(_baton) {}; + UnregisterWorker(FilterUnregisterBaton *_baton, Nan::Callback *callback) + : nodegit::AsyncWorker(callback, "nodegit:AsyncWorker:FilterRegistry:Unregister"), baton(_baton) {}; ~UnregisterWorker() {}; void Execute(); void HandleOKCallback(); + nodegit::LockMaster AcquireLocks(); private: FilterUnregisterBaton *baton; diff --git a/generate/templates/manual/include/lock_master.h b/generate/templates/manual/include/lock_master.h index 911760bd8..54ca0e64a 100644 --- a/generate/templates/manual/include/lock_master.h +++ b/generate/templates/manual/include/lock_master.h @@ -3,165 +3,176 @@ #include -class LockMasterImpl; +namespace nodegit { + class LockMasterImpl; -class LockMaster { -private: - LockMasterImpl *impl; - - template - void AddLocks(const T *t) { - // by default, don't lock anything - } - - // base case for variadic template unwinding - void AddParameters() { - } + class LockMaster { + private: + LockMasterImpl *impl; - // processes a single parameter, then calls recursively on the rest - template - void AddParameters(const T *t, const Types*... args) { - if(t) { - AddLocks(t); + template + void AddLocks(const T *t) { + // by default, don't lock anything } - AddParameters(args...); - } - void ConstructorImpl(); - void DestructorImpl(); - void ObjectToLock(const void *); - void ObjectsToLockAdded(); -public: - - // we lock on construction - template LockMaster(bool asyncAction, const Types*... types) { - if(!asyncAction) { - impl = NULL; - return; + // base case for variadic template unwinding + void AddParameters() { } - ConstructorImpl(); - AddParameters(types...); - ObjectsToLockAdded(); - } - - // and unlock on destruction - ~LockMaster() { - if(!impl) { - return; + // processes a single parameter, then calls recursively on the rest + template + void AddParameters(const T *t, const Types*... args) { + if(t) { + AddLocks(t); + } + AddParameters(args...); } - DestructorImpl(); - } - - // TemporaryUnlock unlocks the LockMaster currently registered on the thread, - // and re-locks it on destruction. - class TemporaryUnlock { - LockMasterImpl *impl; void ConstructorImpl(); void DestructorImpl(); + void ObjectToLock(const void *); + void ObjectsToLockAdded(); public: - TemporaryUnlock() { - // We can't return here if disabled - // It's possible that a LockMaster was fully constructed and registered - // before the thread safety was disabled. - // So we rely on ConstructorImpl to abort if there is no registered LockMaster + + // we lock on construction + template LockMaster(bool asyncAction, const Types*... types) { + if(!asyncAction) { + impl = nullptr; + return; + } + ConstructorImpl(); + AddParameters(types...); + ObjectsToLockAdded(); } - ~TemporaryUnlock() { + + // we don't want this object to be copyable, there can only be one lock holder + LockMaster(const LockMaster &other) = delete; + + LockMaster &operator=(const LockMaster &other) = delete; + + // expose a move constructor so that LockMaster can be returned + LockMaster(LockMaster &&other); + + LockMaster &operator=(LockMaster &&other); + + // and unlock on destruction + ~LockMaster() { if(!impl) { return; } DestructorImpl(); } - }; - static void InitializeGlobal(); - static void InitializeContext(); -}; + // TemporaryUnlock unlocks the LockMaster currently registered on the thread, + // and re-locks it on destruction. + class TemporaryUnlock { + LockMasterImpl *impl; + + void ConstructorImpl(); + void DestructorImpl(); + public: + TemporaryUnlock() { + // We can't return here if disabled + // It's possible that a LockMaster was fully constructed and registered + // before the thread safety was disabled. + // So we rely on ConstructorImpl to abort if there is no registered LockMaster + ConstructorImpl(); + } + ~TemporaryUnlock() { + if(!impl) { + return; + } + DestructorImpl(); + } + }; + static void InitializeGlobal(); + static void InitializeContext(); + }; -template<> inline void LockMaster::AddLocks(const git_repository *repo) { - // when using a repo, lock the repo - ObjectToLock(repo); -} -template<> inline void LockMaster::AddLocks(const git_index *index) { - // when using an index, lock the repo, or if there isn't one lock the index - const void *owner = git_index_owner(index); - if(!owner) { - owner = index; + template<> inline void LockMaster::AddLocks(const git_repository *repo) { + // when using a repo, lock the repo + ObjectToLock(repo); } - ObjectToLock(owner); -} -template<> inline void LockMaster::AddLocks(const git_commit *commit) { - // when using a commit, lock the repo - const void *owner = git_commit_owner(commit); - ObjectToLock(owner); -} + template<> inline void LockMaster::AddLocks(const git_index *index) { + // when using an index, lock the repo, or if there isn't one lock the index + const void *owner = git_index_owner(index); + if(!owner) { + owner = index; + } + ObjectToLock(owner); + } -// ... more locking rules would go here. According to an analysis of idefs.json, -// the following types are passed as non-const * and may require locking -// (some likely, some probably not): -// 'git_annotated_commit', -// 'git_blame_options', -// 'git_blob', -// 'git_buf', -// 'git_checkout_options', -// 'git_cherrypick_options', -// 'git_clone_options', -// 'git_commit', -// 'git_config', -// 'git_diff', -// 'git_diff_perfdata', -// 'git_error', -// 'git_fetch_options', -// 'git_fetch_options', -// 'git_filter', -// 'git_filter_list', -// 'git_hashsig', -// 'git_index', -// 'git_merge_file_input', -// 'git_merge_options', -// 'git_merge_options', -// 'git_note', -// 'git_note_iterator', -// 'git_object', -// 'git_odb', -// 'git_odb_object', -// 'git_oid', -// 'git_oidarray', -// 'git_packbuilder', -// 'git_patch', -// 'git_pathspec', -// 'git_push_options', -// 'git_rebase', -// 'git_rebase_options', -// 'git_refdb', -// 'git_reference', -// 'git_reflog', -// 'git_remote', -// 'git_remote_callbacks', -// 'git_remote_callbacks', -// 'git_repository', -// 'git_repository_init_options', -// 'git_revwalk', -// 'git_signature', -// 'git_stash_apply_options', -// 'git_status_list', -// 'git_strarray', -// 'git_submodule', -// 'git_submodule_update_options', -// 'git_tag', -// 'git_transfer_progress', -// 'git_transport', -// 'git_tree', -// 'git_treebuilder', -// 'git_writestream' -// -// Other types are always passed as const * and perhaps don't require locking -// (it's not a guarantee though) + template<> inline void LockMaster::AddLocks(const git_commit *commit) { + // when using a commit, lock the repo + const void *owner = git_commit_owner(commit); + ObjectToLock(owner); + } + // ... more locking rules would go here. According to an analysis of idefs.json, + // the following types are passed as non-const * and may require locking + // (some likely, some probably not): + // 'git_annotated_commit', + // 'git_blame_options', + // 'git_blob', + // 'git_buf', + // 'git_checkout_options', + // 'git_cherrypick_options', + // 'git_clone_options', + // 'git_commit', + // 'git_config', + // 'git_diff', + // 'git_diff_perfdata', + // 'git_error', + // 'git_fetch_options', + // 'git_fetch_options', + // 'git_filter', + // 'git_filter_list', + // 'git_hashsig', + // 'git_index', + // 'git_merge_file_input', + // 'git_merge_options', + // 'git_merge_options', + // 'git_note', + // 'git_note_iterator', + // 'git_object', + // 'git_odb', + // 'git_odb_object', + // 'git_oid', + // 'git_oidarray', + // 'git_packbuilder', + // 'git_patch', + // 'git_pathspec', + // 'git_push_options', + // 'git_rebase', + // 'git_rebase_options', + // 'git_refdb', + // 'git_reference', + // 'git_reflog', + // 'git_remote', + // 'git_remote_callbacks', + // 'git_remote_callbacks', + // 'git_repository', + // 'git_repository_init_options', + // 'git_revwalk', + // 'git_signature', + // 'git_stash_apply_options', + // 'git_status_list', + // 'git_strarray', + // 'git_submodule', + // 'git_submodule_update_options', + // 'git_tag', + // 'git_transfer_progress', + // 'git_transport', + // 'git_tree', + // 'git_treebuilder', + // 'git_writestream' + // + // Other types are always passed as const * and perhaps don't require locking + // (it's not a guarantee though) +} #endif diff --git a/generate/templates/manual/include/nodegit.h b/generate/templates/manual/include/nodegit.h index a9cef2950..bab3e4179 100644 --- a/generate/templates/manual/include/nodegit.h +++ b/generate/templates/manual/include/nodegit.h @@ -1,10 +1,6 @@ #ifndef NODEGIT_H #define NODEGIT_H -#include "thread_pool.h" - -extern ThreadPool libgit2ThreadPool; - v8::Local GetPrivate(v8::Local object, v8::Local key); diff --git a/generate/templates/manual/include/nodegit_wrapper.h b/generate/templates/manual/include/nodegit_wrapper.h index c40b7af1d..b48e956d1 100644 --- a/generate/templates/manual/include/nodegit_wrapper.h +++ b/generate/templates/manual/include/nodegit_wrapper.h @@ -37,12 +37,10 @@ class NodeGitWrapper : public Nan::ObjectWrap { // CopyablePersistentTraits are used to get the reset-on-destruct behavior. Nan::Persistent > owner; - static Nan::Persistent constructor_template; - // diagnostic count of self-freeing object instances - static int SelfFreeingInstanceCount; + thread_local static int SelfFreeingInstanceCount; // diagnostic count of constructed non-self-freeing object instances - static int NonSelfFreeingConstructedCount; + thread_local static int NonSelfFreeingConstructedCount; static void InitializeTemplate(v8::Local &tpl); diff --git a/generate/templates/manual/include/promise_completion.h b/generate/templates/manual/include/promise_completion.h index 600fc0617..da933b7de 100644 --- a/generate/templates/manual/include/promise_completion.h +++ b/generate/templates/manual/include/promise_completion.h @@ -4,6 +4,7 @@ #include #include "async_baton.h" +#include "context.h" // PromiseCompletion forwards either the resolved result or the rejection reason // to the native layer, once the promise completes @@ -14,33 +15,28 @@ class PromiseCompletion : public Nan::ObjectWrap { // callback type called when a promise completes - typedef void (*Callback) (bool isFulfilled, AsyncBaton *baton, v8::Local resultOfPromise); + typedef void (*Callback) (bool isFulfilled, nodegit::AsyncBaton *baton, v8::Local resultOfPromise); static NAN_METHOD(New); static NAN_METHOD(PromiseFulfilled); static NAN_METHOD(PromiseRejected); - // persistent handles for NAN_METHODs - static Nan::Persistent newFn; - static Nan::Persistent promiseFulfilled; - static Nan::Persistent promiseRejected; - - static v8::Local Bind(Nan::Persistent &method, v8::Local object); + static v8::Local Bind(v8::Local method, v8::Local object); static void CallCallback(bool isFulfilled, const Nan::FunctionCallbackInfo &info); // callback and baton stored for the promise that this PromiseCompletion is // attached to. when the promise completes, the callback will be called with // the result, and the stored baton. Callback callback; - AsyncBaton *baton; + nodegit::AsyncBaton *baton; - void Setup(v8::Local thenFn, v8::Local result, AsyncBaton *baton, Callback callback); + void Setup(v8::Local thenFn, v8::Local result, nodegit::AsyncBaton *baton, Callback callback); public: // If result is a promise, this will instantiate a new PromiseCompletion // and have it forward the promise result / reason via the baton and callback - static bool ForwardIfPromise(v8::Local result, AsyncBaton *baton, Callback callback); + static bool ForwardIfPromise(v8::Local result, nodegit::AsyncBaton *baton, Callback callback); - static void InitializeComponent(); + static void InitializeComponent(nodegit::Context *nodegitContext); }; #endif diff --git a/generate/templates/manual/include/semaphore.h b/generate/templates/manual/include/semaphore.h deleted file mode 100644 index 02f1df446..000000000 --- a/generate/templates/manual/include/semaphore.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef NODEGIT_SEMAPHORE -#define NODEGIT_SEMAPHORE - -#include -#include - -class Semaphore { -public: - Semaphore(); - - void post(); - void wait(); - -private: - std::mutex mutex; - std::condition_variable condition; - unsigned long count; -}; - -#endif diff --git a/generate/templates/manual/include/thread_pool.h b/generate/templates/manual/include/thread_pool.h index 12a3ebe3c..5baf4e743 100644 --- a/generate/templates/manual/include/thread_pool.h +++ b/generate/templates/manual/include/thread_pool.h @@ -1,76 +1,49 @@ #ifndef THREAD_POOL_H #define THREAD_POOL_H +#include +#include +#include #include -#include -#include -#include -#include "./semaphore.h" +#include "async_worker.h" -class ThreadPool { -public: - typedef void (*Callback) (void *); +namespace nodegit { + class ThreadPoolImpl; -private: - struct Work { - Callback workCallback; - Callback completionCallback; - void *data; + class ThreadPool { + public: + typedef std::function Callback; + typedef std::function QueueCallbackFn; + typedef std::function OnPostCallbackFn; - Work(Callback workCallback, Callback completionCallback, void *data) - : workCallback(workCallback), completionCallback(completionCallback), data(data) - {} + // Initializes thread pool and spins up the requested number of threads + // The provided loop will be used for completion callbacks, whenever + // queued work is completed + ThreadPool(int numberOfThreads, uv_loop_t *loop); - Work() - : workCallback(NULL), completionCallback(NULL), data(NULL) - {} - }; - - struct LoopCallback { - Callback callback; - void *data; - bool isWork; + ~ThreadPool(); - LoopCallback(Callback callback, void *data, bool isWork) - : callback(callback), data(data), isWork(isWork) - {} - - LoopCallback() - : callback(NULL), data(NULL), isWork(false) - {} - }; + // Queues work on the thread pool, followed by completion call scheduled + // on the loop provided in the constructor. + // QueueWork should be called on the loop provided in the constructor. + void QueueWorker(nodegit::AsyncWorker *worker); - // work to be performed on the threadpool - std::queue workQueue; - std::mutex workMutex; - Semaphore workSemaphore; - int workInProgressCount; + // When an AsyncWorker is being executed, the threads involved in executing + // will ensure that this is set to the AsyncResource belonging to the AsyncWorker. + // This ensures that any callbacks from libgit2 take the correct AsyncResource + // when scheduling work on the JS thread. + static Nan::AsyncResource *GetCurrentAsyncResource(); - // completion and async callbacks to be performed on the loop - std::queue loopQueue; - std::mutex loopMutex; - uv_async_t loopAsync; + // Queues a callback on the loop provided in the constructor + static void PostCallbackEvent(OnPostCallbackFn onPostCallback); - std::vector threads; + // Called once at libgit2 initialization to setup contracts with libgit2 + static void InitializeGlobal(); - void RunEventQueue(); - static void RunLoopCallbacks(uv_async_t* handle); - void RunLoopCallbacks(); - - void QueueLoopCallback(Callback callback, void *data, bool isWork); - -public: - // Initializes thread pool and spins up the requested number of threads - // The provided loop will be used for completion callbacks, whenever - // queued work is completed - ThreadPool(int numberOfThreads, uv_loop_t *loop); - // Queues work on the thread pool, followed by completion call scheduled - // on the loop provided in the constructor. - // QueueWork should be called on the loop provided in the constructor. - void QueueWork(Callback workCallback, Callback completionCallback, void *data); - // Queues a callback on the loop provided in the constructor - void ExecuteReverseCallback(Callback reverseCallback, void *data); -}; + private: + std::unique_ptr impl; + }; +} #endif diff --git a/generate/templates/manual/include/wrapper.h b/generate/templates/manual/include/wrapper.h index 9dcbe3186..f24ce800b 100644 --- a/generate/templates/manual/include/wrapper.h +++ b/generate/templates/manual/include/wrapper.h @@ -9,15 +9,14 @@ #include #include "nan.h" +#include "context.h" using namespace node; using namespace v8; class Wrapper : public Nan::ObjectWrap { public: - - static Nan::Persistent constructor_template; - static void InitializeComponent (v8::Local target); + static void InitializeComponent (v8::Local target, nodegit::Context *nodegitContext); void *GetValue(); static v8::Local New(const void *raw); diff --git a/generate/templates/manual/patches/convenient_patches.cc b/generate/templates/manual/patches/convenient_patches.cc index 795dca506..5630de1dc 100644 --- a/generate/templates/manual/patches/convenient_patches.cc +++ b/generate/templates/manual/patches/convenient_patches.cc @@ -21,57 +21,60 @@ NAN_METHOD(GitPatch::ConvenientFromDiff) { worker->SaveToPersistent("diff", info[0]); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitPatch::ConvenientFromDiffWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true, baton->diff); + return lockMaster; +} + void GitPatch::ConvenientFromDiffWorker::Execute() { git_error_clear(); - { - LockMaster lockMaster(true, baton->diff); - std::vector patchesToBeFreed; - - for (int i = 0; i < git_diff_num_deltas(baton->diff); ++i) { - git_patch *nextPatch; - int result = git_patch_from_diff(&nextPatch, baton->diff, i); + std::vector patchesToBeFreed; - if (result) { - while (!patchesToBeFreed.empty()) - { - git_patch_free(patchesToBeFreed.back()); - patchesToBeFreed.pop_back(); - } + for (int i = 0; i < git_diff_num_deltas(baton->diff); ++i) { + git_patch *nextPatch; + int result = git_patch_from_diff(&nextPatch, baton->diff, i); - while (!baton->out->empty()) { - PatchDataFree(baton->out->back()); - baton->out->pop_back(); - } - - baton->error_code = result; + if (result) { + while (!patchesToBeFreed.empty()) + { + git_patch_free(patchesToBeFreed.back()); + patchesToBeFreed.pop_back(); + } - if (git_error_last() != NULL) { - baton->error = git_error_dup(git_error_last()); - } + while (!baton->out->empty()) { + PatchDataFree(baton->out->back()); + baton->out->pop_back(); + } - delete baton->out; - baton->out = NULL; + baton->error_code = result; - return; + if (git_error_last() != NULL) { + baton->error = git_error_dup(git_error_last()); } - if (nextPatch != NULL) { - baton->out->push_back(createFromRaw(nextPatch)); - patchesToBeFreed.push_back(nextPatch); - } + delete baton->out; + baton->out = NULL; + + return; } - while (!patchesToBeFreed.empty()) - { - git_patch_free(patchesToBeFreed.back()); - patchesToBeFreed.pop_back(); + if (nextPatch != NULL) { + baton->out->push_back(createFromRaw(nextPatch)); + patchesToBeFreed.push_back(nextPatch); } } + + while (!patchesToBeFreed.empty()) + { + git_patch_free(patchesToBeFreed.back()); + patchesToBeFreed.pop_back(); + } } void GitPatch::ConvenientFromDiffWorker::HandleOKCallback() { diff --git a/generate/templates/manual/remote/ls.cc b/generate/templates/manual/remote/ls.cc index 8816e0150..f3adfee9f 100644 --- a/generate/templates/manual/remote/ls.cc +++ b/generate/templates/manual/remote/ls.cc @@ -14,41 +14,40 @@ NAN_METHOD(GitRemote::ReferenceList) Nan::Callback *callback = new Nan::Callback(Local::Cast(info[0])); ReferenceListWorker *worker = new ReferenceListWorker(baton, callback); worker->SaveToPersistent("remote", info.This()); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitRemote::ReferenceListWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true, baton->remote); + return lockMaster; +} + void GitRemote::ReferenceListWorker::Execute() { git_error_clear(); - { - LockMaster lockMaster( - /*asyncAction: */true, - baton->remote - ); - - const git_remote_head **remote_heads; - size_t num_remote_heads; - baton->error_code = git_remote_ls( - &remote_heads, - &num_remote_heads, - baton->remote - ); + const git_remote_head **remote_heads; + size_t num_remote_heads; + baton->error_code = git_remote_ls( + &remote_heads, + &num_remote_heads, + baton->remote + ); - if (baton->error_code != GIT_OK) { - baton->error = git_error_dup(git_error_last()); - delete baton->out; - baton->out = NULL; - return; - } + if (baton->error_code != GIT_OK) { + baton->error = git_error_dup(git_error_last()); + delete baton->out; + baton->out = NULL; + return; + } - baton->out->reserve(num_remote_heads); + baton->out->reserve(num_remote_heads); - for (size_t head_index = 0; head_index < num_remote_heads; ++head_index) { - git_remote_head *remote_head = git_remote_head_dup(remote_heads[head_index]); - baton->out->push_back(remote_head); - } + for (size_t head_index = 0; head_index < num_remote_heads; ++head_index) { + git_remote_head *remote_head = git_remote_head_dup(remote_heads[head_index]); + baton->out->push_back(remote_head); } } diff --git a/generate/templates/manual/repository/get_references.cc b/generate/templates/manual/repository/get_references.cc index 8f03d60e1..313f0479b 100644 --- a/generate/templates/manual/repository/get_references.cc +++ b/generate/templates/manual/repository/get_references.cc @@ -14,15 +14,20 @@ NAN_METHOD(GitRepository::GetReferences) Nan::Callback *callback = new Nan::Callback(Local::Cast(info[0])); GetReferencesWorker *worker = new GetReferencesWorker(baton, callback); worker->SaveToPersistent("repo", info.This()); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitRepository::GetReferencesWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true, baton->repo); + return lockMaster; +} + void GitRepository::GetReferencesWorker::Execute() { giterr_clear(); - LockMaster lockMaster(true, baton->repo); git_repository *repo = baton->repo; git_strarray reference_names; diff --git a/generate/templates/manual/repository/get_remotes.cc b/generate/templates/manual/repository/get_remotes.cc index 9cad189a2..eb562de9f 100644 --- a/generate/templates/manual/repository/get_remotes.cc +++ b/generate/templates/manual/repository/get_remotes.cc @@ -14,17 +14,23 @@ NAN_METHOD(GitRepository::GetRemotes) Nan::Callback *callback = new Nan::Callback(Local::Cast(info[0])); GetRemotesWorker *worker = new GetRemotesWorker(baton, callback); worker->SaveToPersistent("repo", info.This()); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitRepository::GetRemotesWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true); + return lockMaster; +} + void GitRepository::GetRemotesWorker::Execute() { giterr_clear(); git_repository *repo; { - LockMaster lockMaster(true, baton->repo); + nodegit::LockMaster lockMaster(true, baton->repo); baton->error_code = git_repository_open(&repo, git_repository_workdir(baton->repo)); } diff --git a/generate/templates/manual/repository/get_submodules.cc b/generate/templates/manual/repository/get_submodules.cc index 71de6948c..425754ad9 100644 --- a/generate/templates/manual/repository/get_submodules.cc +++ b/generate/templates/manual/repository/get_submodules.cc @@ -14,7 +14,8 @@ NAN_METHOD(GitRepository::GetSubmodules) Nan::Callback *callback = new Nan::Callback(Local::Cast(info[0])); GetSubmodulesWorker *worker = new GetSubmodulesWorker(baton, callback); worker->SaveToPersistent("repo", info.This()); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } @@ -35,12 +36,15 @@ int foreachSubmoduleCB(git_submodule *submodule, const char *name, void *void_pa return result; } +nodegit::LockMaster GitRepository::GetSubmodulesWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true, baton->repo); + return lockMaster; +} + void GitRepository::GetSubmodulesWorker::Execute() { giterr_clear(); - LockMaster lockMaster(true, baton->repo); - submodule_foreach_payload payload { baton->repo, baton->out }; baton->error_code = git_submodule_foreach(baton->repo, foreachSubmoduleCB, (void *)&payload); diff --git a/generate/templates/manual/repository/refresh_references.cc b/generate/templates/manual/repository/refresh_references.cc index 730afadbc..80a783708 100644 --- a/generate/templates/manual/repository/refresh_references.cc +++ b/generate/templates/manual/repository/refresh_references.cc @@ -406,15 +406,21 @@ NAN_METHOD(GitRepository::RefreshReferences) RefreshReferencesWorker *worker = new RefreshReferencesWorker(baton, callback); worker->SaveToPersistent("repo", info.This()); worker->SaveToPersistent("signatureType", signatureType); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitRepository::RefreshReferencesWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true, baton->repo); + return lockMaster; +} + void GitRepository::RefreshReferencesWorker::Execute() { giterr_clear(); - LockMaster lockMaster(true, baton->repo); + nodegit::LockMaster lockMaster(true, baton->repo); git_repository *repo = baton->repo; RefreshReferencesData *refreshData = (RefreshReferencesData *)baton->out; git_odb *odb; diff --git a/generate/templates/manual/revwalk/commit_walk.cc b/generate/templates/manual/revwalk/commit_walk.cc index bef5bc889..b2486e899 100644 --- a/generate/templates/manual/revwalk/commit_walk.cc +++ b/generate/templates/manual/revwalk/commit_walk.cc @@ -145,10 +145,16 @@ NAN_METHOD(GitRevwalk::CommitWalk) { CommitWalkWorker *worker = new CommitWalkWorker(baton, callback); worker->SaveToPersistent("commitWalk", info.This()); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitRevwalk::CommitWalkWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true); + return lockMaster; +} + void GitRevwalk::CommitWalkWorker::Execute() { giterr_clear(); diff --git a/generate/templates/manual/revwalk/fast_walk.cc b/generate/templates/manual/revwalk/fast_walk.cc index 002251852..72d092eba 100644 --- a/generate/templates/manual/revwalk/fast_walk.cc +++ b/generate/templates/manual/revwalk/fast_walk.cc @@ -21,10 +21,16 @@ NAN_METHOD(GitRevwalk::FastWalk) FastWalkWorker *worker = new FastWalkWorker(baton, callback); worker->SaveToPersistent("fastWalk", info.This()); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitRevwalk::FastWalkWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true); + return lockMaster; +} + void GitRevwalk::FastWalkWorker::Execute() { for (int i = 0; i < baton->max_count; i++) diff --git a/generate/templates/manual/revwalk/file_history_walk.cc b/generate/templates/manual/revwalk/file_history_walk.cc index d8d2935df..6ba2e0b33 100644 --- a/generate/templates/manual/revwalk/file_history_walk.cc +++ b/generate/templates/manual/revwalk/file_history_walk.cc @@ -207,10 +207,16 @@ NAN_METHOD(GitRevwalk::FileHistoryWalk) FileHistoryWalkWorker *worker = new FileHistoryWalkWorker(baton, callback); worker->SaveToPersistent("fileHistoryWalk", info.This()); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitRevwalk::FileHistoryWalkWorker::AcquireLocks() { + nodegit::LockMaster lockMaster(true); + return lockMaster; +} + void GitRevwalk::FileHistoryWalkWorker::Execute() { git_repository *repo = git_revwalk_repository(baton->walk); diff --git a/generate/templates/manual/src/async_baton.cc b/generate/templates/manual/src/async_baton.cc index 590a19c62..da99b9e26 100644 --- a/generate/templates/manual/src/async_baton.cc +++ b/generate/templates/manual/src/async_baton.cc @@ -1,5 +1,69 @@ #include "../include/async_baton.h" -void deleteBaton(AsyncBaton *baton) { - delete baton; +namespace nodegit { + void deleteBaton(AsyncBaton *baton) { + delete baton; + } + + AsyncBaton::AsyncBaton() + : asyncResource(ThreadPool::GetCurrentAsyncResource()), completedMutex(new std::mutex), hasCompleted(false) + {} + + void AsyncBaton::SignalCompletion() { + std::lock_guard lock(*completedMutex); + hasCompleted = true; + completedCondition.notify_one(); + } + + void AsyncBaton::Done() { + onCompletion(); + } + + Nan::AsyncResource *AsyncBaton::GetAsyncResource() { + return asyncResource; + } + + void AsyncBaton::ExecuteAsyncPerform(AsyncCallback asyncCallback, CompletionCallback onCompletion) { + auto jsCallback = [asyncCallback, this]() { + asyncCallback(this); + }; + + if (onCompletion) { + this->onCompletion = [this, onCompletion]() { + onCompletion(this); + }; + + ThreadPool::PostCallbackEvent( + [this, jsCallback]( + ThreadPool::QueueCallbackFn queueCallback, + ThreadPool::Callback callbackCompleted + ) -> ThreadPool::Callback { + queueCallback(jsCallback); + callbackCompleted(); + + return []() {}; + } + ); + } else { + ThreadPool::PostCallbackEvent( + [this, jsCallback]( + ThreadPool::QueueCallbackFn queueCallback, + ThreadPool::Callback callbackCompleted + ) -> ThreadPool::Callback { + this->onCompletion = callbackCompleted; + + queueCallback(jsCallback); + + return std::bind(&AsyncBaton::SignalCompletion, this); + } + ); + + WaitForCompletion(); + } + } + + void AsyncBaton::WaitForCompletion() { + std::unique_lock lock(*completedMutex); + while (!hasCompleted) completedCondition.wait(lock); + } } diff --git a/generate/templates/manual/src/async_worker.cc b/generate/templates/manual/src/async_worker.cc new file mode 100644 index 000000000..65c7a4e1c --- /dev/null +++ b/generate/templates/manual/src/async_worker.cc @@ -0,0 +1,11 @@ +#include "../include/async_worker.h" + +namespace nodegit { + AsyncWorker::AsyncWorker(Nan::Callback *callback, const char *resourceName) + : Nan::AsyncWorker(callback, resourceName) + {} + + Nan::AsyncResource *AsyncWorker::GetAsyncResource() { + return async_resource; + } +} diff --git a/generate/templates/manual/src/context.cc b/generate/templates/manual/src/context.cc new file mode 100644 index 000000000..b5454b6e4 --- /dev/null +++ b/generate/templates/manual/src/context.cc @@ -0,0 +1,42 @@ +#include "../include/context.h" + +namespace nodegit { + Context::Context(v8::Isolate *isolate) + : isolate(isolate), threadPool(10, node::GetCurrentEventLoop(isolate)) + { + Nan::HandleScope scopoe; + v8::Local storage = Nan::New(); + persistentStorage.Reset(storage); + contexts[isolate] = this; + } + + Context::~Context() { + contexts.erase(isolate); + } + + void Context::QueueWorker(nodegit::AsyncWorker *worker) { + threadPool.QueueWorker(worker); + } + + void Context::SaveToPersistent(std::string key, const v8::Local &value) { + Nan::HandleScope scope; + v8::Local storage = Nan::New(persistentStorage); + Nan::Set(storage, Nan::New(key).ToLocalChecked(), value); + } + + v8::Local Context::GetFromPersistent(std::string key) { + Nan::EscapableHandleScope scope; + v8::Local storage = Nan::New(persistentStorage); + Nan::MaybeLocal value = Nan::Get(storage, Nan::New(key).ToLocalChecked()); + return scope.Escape(value.ToLocalChecked()); + } + + Context *Context::GetCurrentContext() { + Nan::HandleScope scope; + v8::Local context = Nan::GetCurrentContext(); + v8::Isolate *isolate = context->GetIsolate(); + return contexts[isolate]; + } + + std::map Context::contexts; +} diff --git a/generate/templates/manual/src/convenient_hunk.cc b/generate/templates/manual/src/convenient_hunk.cc index 184f015a1..ff1c0967b 100644 --- a/generate/templates/manual/src/convenient_hunk.cc +++ b/generate/templates/manual/src/convenient_hunk.cc @@ -5,6 +5,7 @@ extern "C" { #include } +#include "../include/context.h" #include "../include/functions/copy.h" #include "../include/convenient_hunk.h" #include "../include/diff_line.h" @@ -32,27 +33,28 @@ ConvenientHunk::~ConvenientHunk() { HunkDataFree(this->hunk); } -void ConvenientHunk::InitializeComponent(Local target) { +void ConvenientHunk::InitializeComponent(Local target, nodegit::Context *nodegitContext) { Nan::HandleScope scope; - Local tpl = Nan::New(JSNewFunction); + Local nodegitExternal = Nan::New(nodegitContext); + Local tpl = Nan::New(JSNewFunction, nodegitExternal); tpl->InstanceTemplate()->SetInternalFieldCount(1); tpl->SetClassName(Nan::New("ConvenientHunk").ToLocalChecked()); - Nan::SetPrototypeMethod(tpl, "size", Size); - Nan::SetPrototypeMethod(tpl, "lines", Lines); + Nan::SetPrototypeMethod(tpl, "size", Size, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "lines", Lines, nodegitExternal); - Nan::SetPrototypeMethod(tpl, "oldStart", OldStart); - Nan::SetPrototypeMethod(tpl, "oldLines", OldLines); - Nan::SetPrototypeMethod(tpl, "newStart", NewStart); - Nan::SetPrototypeMethod(tpl, "newLines", NewLines); - Nan::SetPrototypeMethod(tpl, "headerLen", HeaderLen); - Nan::SetPrototypeMethod(tpl, "header", Header); + Nan::SetPrototypeMethod(tpl, "oldStart", OldStart, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "oldLines", OldLines, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "newStart", NewStart, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "newLines", NewLines, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "headerLen", HeaderLen, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "header", Header, nodegitExternal); - Local _constructor_template = Nan::GetFunction(tpl).ToLocalChecked(); - constructor_template.Reset(_constructor_template); - Nan::Set(target, Nan::New("ConvenientHunk").ToLocalChecked(), _constructor_template); + Local constructor_template = Nan::GetFunction(tpl).ToLocalChecked(); + nodegitContext->SaveToPersistent("ConvenientHunk::Template", constructor_template); + Nan::Set(target, Nan::New("ConvenientHunk").ToLocalChecked(), constructor_template); } NAN_METHOD(ConvenientHunk::JSNewFunction) { @@ -67,10 +69,12 @@ NAN_METHOD(ConvenientHunk::JSNewFunction) { info.GetReturnValue().Set(info.This()); } -Local ConvenientHunk::New(void *raw) { +Local ConvenientHunk::New(void *raw) { Nan::EscapableHandleScope scope; - Local argv[1] = { Nan::New((void *)raw) }; - return scope.Escape(Nan::NewInstance(Nan::New(ConvenientHunk::constructor_template), 1, argv).ToLocalChecked()); + Local argv[1] = { Nan::New((void *)raw) }; + nodegit::Context *nodegitContext = nodegit::Context::GetCurrentContext(); + Local constructor_template = nodegitContext->GetFromPersistent("ConvenientHunk::Template").As(); + return scope.Escape(Nan::NewInstance(constructor_template, 1, argv).ToLocalChecked()); } HunkData *ConvenientHunk::GetValue() { @@ -101,10 +105,15 @@ NAN_METHOD(ConvenientHunk::Lines) { worker->SaveToPersistent("hunk", info.This()); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster ConvenientHunk::LinesWorker::AcquireLocks() { + return nodegit::LockMaster(true); +} + void ConvenientHunk::LinesWorker::Execute() { baton->lines = new std::vector; baton->lines->reserve(baton->hunk->numLines); @@ -181,5 +190,3 @@ NAN_METHOD(ConvenientHunk::Header) { info.GetReturnValue().Set(to); } - -Nan::Persistent ConvenientHunk::constructor_template; diff --git a/generate/templates/manual/src/convenient_patch.cc b/generate/templates/manual/src/convenient_patch.cc index 9c6cd46ca..01a437fb9 100644 --- a/generate/templates/manual/src/convenient_patch.cc +++ b/generate/templates/manual/src/convenient_patch.cc @@ -5,6 +5,7 @@ extern "C" { #include } +#include "../include/context.h" #include "../include/convenient_hunk.h" #include "../include/convenient_patch.h" #include "../include/functions/copy.h" @@ -129,36 +130,37 @@ ConvenientPatch::~ConvenientPatch() { PatchDataFree(this->patch); } -void ConvenientPatch::InitializeComponent(Local target) { +void ConvenientPatch::InitializeComponent(Local target, nodegit::Context *nodegitContext) { Nan::HandleScope scope; - Local tpl = Nan::New(JSNewFunction); + Local nodegitExternal = Nan::New(nodegitContext); + Local tpl = Nan::New(JSNewFunction, nodegitExternal); tpl->InstanceTemplate()->SetInternalFieldCount(1); tpl->SetClassName(Nan::New("ConvenientPatch").ToLocalChecked()); - Nan::SetPrototypeMethod(tpl, "hunks", Hunks); - Nan::SetPrototypeMethod(tpl, "lineStats", LineStats); - Nan::SetPrototypeMethod(tpl, "size", Size); - - Nan::SetPrototypeMethod(tpl, "oldFile", OldFile); - Nan::SetPrototypeMethod(tpl, "newFile", NewFile); - Nan::SetPrototypeMethod(tpl, "status", Status); - Nan::SetPrototypeMethod(tpl, "isUnmodified", IsUnmodified); - Nan::SetPrototypeMethod(tpl, "isAdded", IsAdded); - Nan::SetPrototypeMethod(tpl, "isDeleted", IsDeleted); - Nan::SetPrototypeMethod(tpl, "isModified", IsModified); - Nan::SetPrototypeMethod(tpl, "isRenamed", IsRenamed); - Nan::SetPrototypeMethod(tpl, "isCopied", IsCopied); - Nan::SetPrototypeMethod(tpl, "isIgnored", IsIgnored); - Nan::SetPrototypeMethod(tpl, "isUntracked", IsUntracked); - Nan::SetPrototypeMethod(tpl, "isTypeChange", IsTypeChange); - Nan::SetPrototypeMethod(tpl, "isUnreadable", IsUnreadable); - Nan::SetPrototypeMethod(tpl, "isConflicted", IsConflicted); - - Local _constructor_template = Nan::GetFunction(tpl).ToLocalChecked(); - constructor_template.Reset(_constructor_template); - Nan::Set(target, Nan::New("ConvenientPatch").ToLocalChecked(), _constructor_template); + Nan::SetPrototypeMethod(tpl, "hunks", Hunks, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "lineStats", LineStats, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "size", Size, nodegitExternal); + + Nan::SetPrototypeMethod(tpl, "oldFile", OldFile, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "newFile", NewFile, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "status", Status, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "isUnmodified", IsUnmodified, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "isAdded", IsAdded, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "isDeleted", IsDeleted, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "isModified", IsModified, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "isRenamed", IsRenamed, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "isCopied", IsCopied, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "isIgnored", IsIgnored, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "isUntracked", IsUntracked, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "isTypeChange", IsTypeChange, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "isUnreadable", IsUnreadable, nodegitExternal); + Nan::SetPrototypeMethod(tpl, "isConflicted", IsConflicted, nodegitExternal); + + Local constructor_template = Nan::GetFunction(tpl).ToLocalChecked(); + nodegitContext->SaveToPersistent("ConvenientPatch::Template", constructor_template); + Nan::Set(target, Nan::New("ConvenientPatch").ToLocalChecked(), constructor_template); } NAN_METHOD(ConvenientPatch::JSNewFunction) { @@ -176,7 +178,9 @@ NAN_METHOD(ConvenientPatch::JSNewFunction) { Local ConvenientPatch::New(void *raw) { Nan::EscapableHandleScope scope; Local argv[1] = { Nan::New((void *)raw) }; - return scope.Escape(Nan::NewInstance(Nan::New(ConvenientPatch::constructor_template), 1, argv).ToLocalChecked()); + nodegit::Context *nodegitContext = nodegit::Context::GetCurrentContext(); + Local constructor_template = nodegitContext->GetFromPersistent("ConvenientPatch::Template").As(); + return scope.Escape(Nan::NewInstance(constructor_template, 1, argv).ToLocalChecked()); } ConvenientLineStats ConvenientPatch::GetLineStats() { @@ -217,10 +221,15 @@ NAN_METHOD(ConvenientPatch::Hunks) { worker->SaveToPersistent("patch", info.This()); - Nan::AsyncQueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster ConvenientPatch::HunksWorker::AcquireLocks() { + return nodegit::LockMaster(true); +} + void ConvenientPatch::HunksWorker::Execute() { // copy hunks baton->hunks = new std::vector; @@ -396,5 +405,3 @@ NAN_METHOD(ConvenientPatch::IsConflicted) { to = Nan::New(Nan::ObjectWrap::Unwrap(info.This())->GetStatus() == GIT_DELTA_CONFLICTED); info.GetReturnValue().Set(to); } - -Nan::Persistent ConvenientPatch::constructor_template; diff --git a/generate/templates/manual/src/filter_registry.cc b/generate/templates/manual/src/filter_registry.cc index 67e958d03..688b9efbf 100644 --- a/generate/templates/manual/src/filter_registry.cc +++ b/generate/templates/manual/src/filter_registry.cc @@ -6,11 +6,11 @@ extern "C" { } #include "../include/nodegit.h" +#include "../include/context.h" #include "../include/lock_master.h" #include "../include/functions/copy.h" #include "../include/filter_registry.h" #include "nodegit_wrapper.cc" -#include "../include/async_libgit2_queue_worker.h" #include "../include/filter.h" @@ -18,19 +18,18 @@ using namespace std; using namespace v8; using namespace node; -Nan::Persistent GitFilterRegistry::persistentHandle; - // #pragma unmanaged -void GitFilterRegistry::InitializeComponent(v8::Local target) { +void GitFilterRegistry::InitializeComponent(v8::Local target, nodegit::Context *nodegitContext) { Nan::HandleScope scope; - v8::Local object = Nan::New(); + v8::Local filterRegistry = Nan::New(); - Nan::SetMethod(object, "register", GitFilterRegister); - Nan::SetMethod(object, "unregister", GitFilterUnregister); + Local nodegitExternal = Nan::New(nodegitContext); + Nan::SetMethod(filterRegistry, "register", GitFilterRegister, nodegitExternal); + Nan::SetMethod(filterRegistry, "unregister", GitFilterUnregister, nodegitExternal); - Nan::Set(target, Nan::New("FilterRegistry").ToLocalChecked(), object); - GitFilterRegistry::persistentHandle.Reset(object); + Nan::Set(target, Nan::New("FilterRegistry").ToLocalChecked(), filterRegistry); + nodegitContext->SaveToPersistent("FilterRegistry", filterRegistry); } NAN_METHOD(GitFilterRegistry::GitFilterRegister) { @@ -64,7 +63,10 @@ NAN_METHOD(GitFilterRegistry::GitFilterRegister) { baton->error_code = GIT_OK; baton->filter_priority = Nan::To(info[2]).FromJust(); - Nan::Set(Nan::New(GitFilterRegistry::persistentHandle), Nan::To(info[0]).ToLocalChecked(), Nan::To(info[1]).ToLocalChecked()); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + + Local filterRegistry = nodegitContext->GetFromPersistent("FilterRegistry").As(); + Nan::Set(filterRegistry, Nan::To(info[0]).ToLocalChecked(), Nan::To(info[1]).ToLocalChecked()); Nan::Callback *callback = new Nan::Callback(Local::Cast(info[3])); RegisterWorker *worker = new RegisterWorker(baton, callback); @@ -72,15 +74,19 @@ NAN_METHOD(GitFilterRegistry::GitFilterRegister) { worker->SaveToPersistent("filter_name", Nan::To(info[0]).ToLocalChecked()); worker->SaveToPersistent("filter_priority", Nan::To(info[2]).ToLocalChecked()); - AsyncLibgit2QueueWorker(worker); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitFilterRegistry::RegisterWorker::AcquireLocks() { + return nodegit::LockMaster(true, baton->filter_name, baton->filter); +} + void GitFilterRegistry::RegisterWorker::Execute() { git_error_clear(); { - LockMaster lockMaster(/*asyncAction: */true, baton->filter_name, baton->filter); + nodegit::LockMaster lockMaster(/*asyncAction: */true, baton->filter_name, baton->filter); int result = git_filter_register(baton->filter_name, baton->filter, baton->filter_priority); baton->error_code = result; @@ -158,15 +164,20 @@ NAN_METHOD(GitFilterRegistry::GitFilterUnregister) { worker->SaveToPersistent("filter_name", info[0]); - AsyncLibgit2QueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster GitFilterRegistry::UnregisterWorker::AcquireLocks() { + return nodegit::LockMaster(true, baton->filter_name); +} + void GitFilterRegistry::UnregisterWorker::Execute() { git_error_clear(); { - LockMaster lockMaster(/*asyncAction: */true, baton->filter_name); + nodegit::LockMaster lockMaster(/*asyncAction: */true, baton->filter_name); int result = git_filter_unregister(baton->filter_name); baton->error_code = result; @@ -178,6 +189,9 @@ void GitFilterRegistry::UnregisterWorker::Execute() { void GitFilterRegistry::UnregisterWorker::HandleOKCallback() { if (baton->error_code == GIT_OK) { + nodegit::Context *nodegitContext = nodegit::Context::GetCurrentContext(); + Local filterRegistry = nodegitContext->GetFromPersistent("FilterRegistry").As(); + Nan::Delete(filterRegistry, Nan::To(GetFromPersistent("filter_name")).ToLocalChecked()); v8::Local result = Nan::New(baton->error_code); v8::Local argv[2] = { Nan::Null(), diff --git a/generate/templates/manual/src/lock_master.cc b/generate/templates/manual/src/lock_master.cc index fd1c49192..72740db8e 100644 --- a/generate/templates/manual/src/lock_master.cc +++ b/generate/templates/manual/src/lock_master.cc @@ -9,213 +9,228 @@ #include #include "../include/lock_master.h" +namespace nodegit { + // information about a lockable object + // - the mutex used to lock it and the number of outstanding locks + struct ObjectInfo { + std::shared_ptr mutex; + unsigned useCount; + + ObjectInfo(unsigned useCount) + : mutex(new std::mutex), useCount(useCount) + {} + }; + + // LockMaster implementation details + // implemented in a separate class to keep LockMaster opaque + class LockMasterImpl { + // STATIC variables / methods + + // A map from objects that are locked (or were locked), to information on their mutex + static std::map mutexes; + // A mutex used for the mutexes map + static std::mutex mapMutex; + + // A thread local storage slot for the current thread-specific LockMasterImpl instance + thread_local static LockMasterImpl* currentLockMaster; + + // Cleans up any mutexes that are not currently used + static NAN_GC_CALLBACK(CleanupMutexes); + + public: + static void InitializeContext(); + + // INSTANCE variables / methods + + private: + // The set of objects this LockMaster is responsible for locking + std::set objectsToLock; + + // Mutexes locked by this LockMaster on construction and unlocked on destruction + std::vector> GetMutexes(int useCountDelta); + void Register(); + void Unregister(); + + public: + static LockMasterImpl *CurrentLockMasterImpl() { + return (LockMasterImpl *)currentLockMaster; + } -// information about a lockable object -// - the mutex used to lock it and the number of outstanding locks -struct ObjectInfo { - std::shared_ptr mutex; - unsigned useCount; - - ObjectInfo(unsigned useCount) - : mutex(new std::mutex), useCount(useCount) - {} -}; - -// LockMaster implementation details -// implemented in a separate class to keep LockMaster opaque -class LockMasterImpl { - // STATIC variables / methods - - // A map from objects that are locked (or were locked), to information on their mutex - static std::map mutexes; - // A mutex used for the mutexes map - static std::mutex mapMutex; - - // A thread local storage slot for the current thread-specific LockMasterImpl instance - thread_local static LockMasterImpl* currentLockMaster; + LockMasterImpl() { + Register(); + } - // Cleans up any mutexes that are not currently used - static NAN_GC_CALLBACK(CleanupMutexes); + ~LockMasterImpl() { + Unregister(); + Unlock(true); + } -public: - static void InitializeContext(); + void ObjectToLock(const void *objectToLock) { + objectsToLock.insert(objectToLock); + } - // INSTANCE variables / methods + void Lock(bool acquireMutexes); + void Unlock(bool releaseMutexes); + }; -private: - // The set of objects this LockMaster is responsible for locking - std::set objectsToLock; + std::map LockMasterImpl::mutexes; + std::mutex LockMasterImpl::mapMutex; + thread_local LockMasterImpl* LockMasterImpl::currentLockMaster = nullptr; - // Mutexes locked by this LockMaster on construction and unlocked on destruction - std::vector> GetMutexes(int useCountDelta); - void Register(); - void Unregister(); + LockMaster::LockMaster(LockMaster &&other) + : impl(std::exchange(other.impl, nullptr)) + {} -public: - static LockMasterImpl *CurrentLockMasterImpl() { - return (LockMasterImpl *)currentLockMaster; - } + LockMaster &LockMaster::operator=(LockMaster &&other) { + if (&other == this) { + return *this; + } - LockMasterImpl() { - Register(); + impl = std::exchange(other.impl, nullptr); + return *this; } - ~LockMasterImpl() { - Unregister(); - Unlock(true); + void LockMasterImpl::InitializeContext() { + Nan::AddGCEpilogueCallback(CleanupMutexes); } - void ObjectToLock(const void *objectToLock) { - objectsToLock.insert(objectToLock); + NAN_GC_CALLBACK(LockMasterImpl::CleanupMutexes) { + std::lock_guard lock(mapMutex); + + for (auto it = mutexes.begin(); it != mutexes.end(); ) + { + // if the mutex is not used by any LockMasters, + // we can destroy it + unsigned useCount = it->second.useCount; + if (!useCount) { + auto to_erase = it; + it++; + mutexes.erase(to_erase); + } else { + it++; + } + } } - void Lock(bool acquireMutexes); - void Unlock(bool releaseMutexes); -}; - -std::map LockMasterImpl::mutexes; -std::mutex LockMasterImpl::mapMutex; -thread_local LockMasterImpl* LockMasterImpl::currentLockMaster = NULL; - -void LockMasterImpl::InitializeContext() { - Nan::AddGCEpilogueCallback(CleanupMutexes); -} - -NAN_GC_CALLBACK(LockMasterImpl::CleanupMutexes) { - std::lock_guard lock(mapMutex); - - for (auto it = mutexes.begin(); it != mutexes.end(); ) - { - // if the mutex is not used by any LockMasters, - // we can destroy it - unsigned useCount = it->second.useCount; - if (!useCount) { - auto to_erase = it; - it++; - mutexes.erase(to_erase); - } else { - it++; - } + void LockMaster::InitializeContext() { + LockMasterImpl::InitializeContext(); } -} -void LockMaster::InitializeContext() { - LockMasterImpl::InitializeContext(); -} + std::vector> LockMasterImpl::GetMutexes(int useCountDelta) { + std::vector> objectMutexes; + std::lock_guard lock(mapMutex); + + for (auto object : objectsToLock) { + if (object) { + // ensure we have an initialized mutex for each object + auto mutexIt = mutexes.find(object); + if (mutexIt == mutexes.end()) { + mutexIt = mutexes.insert( + std::make_pair( + object, + ObjectInfo(0U) + ) + ).first; + } -std::vector> LockMasterImpl::GetMutexes(int useCountDelta) { - std::vector> objectMutexes; - std::lock_guard lock(mapMutex); - - for (auto object : objectsToLock) { - if (object) { - // ensure we have an initialized mutex for each object - auto mutexIt = mutexes.find(object); - if (mutexIt == mutexes.end()) { - mutexIt = mutexes.insert( - std::make_pair( - object, - ObjectInfo(0U) - ) - ).first; + objectMutexes.push_back(mutexIt->second.mutex); + mutexIt->second.useCount += useCountDelta; } - - objectMutexes.push_back(mutexIt->second.mutex); - mutexIt->second.useCount += useCountDelta; } - } - - return objectMutexes; -} -void LockMasterImpl::Register() { - currentLockMaster = this; -} - -void LockMasterImpl::Unregister() { - currentLockMaster = NULL; -} + return objectMutexes; + } -void LockMasterImpl::Lock(bool acquireMutexes) { - std::vector> objectMutexes = GetMutexes(acquireMutexes * 1); - - auto alreadyLocked = objectMutexes.end(); - std::vector>::iterator it; - - // we will attempt to lock all the mutexes at the same time to avoid deadlocks - // note in most cases we are locking 0 or 1 mutexes. more than 1 implies - // passing objects with different repos/owners in the same call. - do { - // go through all the mutexes and try to lock them - for (it = objectMutexes.begin(); it != objectMutexes.end(); it++) { - // if we already locked this mutex in a previous pass via std::mutex::lock, - // we don't need to lock it again - if (it == alreadyLocked) { - continue; - } + void LockMasterImpl::Register() { + currentLockMaster = this; + } - // first, try to lock (non-blocking) - bool success = (*it)->try_lock(); - if (!success) { - // we have failed to lock a mutex... unlock everything we have locked - std::for_each(objectMutexes.begin(), it, [](std::shared_ptr mutex) { - mutex->unlock(); - }); + void LockMasterImpl::Unregister() { + currentLockMaster = nullptr; + } - if (alreadyLocked > it && alreadyLocked != objectMutexes.end()) { - (*alreadyLocked)->unlock(); + void LockMasterImpl::Lock(bool acquireMutexes) { + std::vector> objectMutexes = GetMutexes(acquireMutexes * 1); + + auto alreadyLocked = objectMutexes.end(); + std::vector>::iterator it; + + // we will attempt to lock all the mutexes at the same time to avoid deadlocks + // note in most cases we are locking 0 or 1 mutexes. more than 1 implies + // passing objects with different repos/owners in the same call. + do { + // go through all the mutexes and try to lock them + for (it = objectMutexes.begin(); it != objectMutexes.end(); it++) { + // if we already locked this mutex in a previous pass via std::mutex::lock, + // we don't need to lock it again + if (it == alreadyLocked) { + continue; } - // now do a blocking lock on what we couldn't lock - (*it)->lock(); - // mark that we have already locked this one - // if there are more mutexes than this one, we will go back to locking everything - alreadyLocked = it; - break; + // first, try to lock (non-blocking) + bool success = (*it)->try_lock(); + if (!success) { + // we have failed to lock a mutex... unlock everything we have locked + std::for_each(objectMutexes.begin(), it, [](std::shared_ptr mutex) { + mutex->unlock(); + }); + + if (alreadyLocked > it && alreadyLocked != objectMutexes.end()) { + (*alreadyLocked)->unlock(); + } + + // now do a blocking lock on what we couldn't lock + (*it)->lock(); + // mark that we have already locked this one + // if there are more mutexes than this one, we will go back to locking everything + alreadyLocked = it; + break; + } } - } - } while (it != objectMutexes.end()); -} + } while (it != objectMutexes.end()); + } -void LockMasterImpl::Unlock(bool releaseMutexes) { - // Get the mutexes but don't decrement their use count until after we've - // unlocked them all. - std::vector> objectMutexes = GetMutexes(0); + void LockMasterImpl::Unlock(bool releaseMutexes) { + // Get the mutexes but don't decrement their use count until after we've + // unlocked them all. + std::vector> objectMutexes = GetMutexes(0); - std::for_each(objectMutexes.begin(), objectMutexes.end(), [](std::shared_ptr mutex) { - mutex->unlock(); - }); + std::for_each(objectMutexes.begin(), objectMutexes.end(), [](std::shared_ptr mutex) { + mutex->unlock(); + }); - GetMutexes(releaseMutexes * -1); -} + GetMutexes(releaseMutexes * -1); + } -// LockMaster + // LockMaster -void LockMaster::ConstructorImpl() { - impl = new LockMasterImpl(); -} + void LockMaster::ConstructorImpl() { + impl = new LockMasterImpl(); + } -void LockMaster::DestructorImpl() { - delete impl; -} + void LockMaster::DestructorImpl() { + delete impl; + } -void LockMaster::ObjectToLock(const void *objectToLock) { - impl->ObjectToLock(objectToLock); -} + void LockMaster::ObjectToLock(const void *objectToLock) { + impl->ObjectToLock(objectToLock); + } -void LockMaster::ObjectsToLockAdded() { - impl->Lock(true); -} + void LockMaster::ObjectsToLockAdded() { + impl->Lock(true); + } + + // LockMaster::TemporaryUnlock -// LockMaster::TemporaryUnlock + void LockMaster::TemporaryUnlock::ConstructorImpl() { + impl = LockMasterImpl::CurrentLockMasterImpl(); + if (impl) { + impl->Unlock(false); + } + } -void LockMaster::TemporaryUnlock::ConstructorImpl() { - impl = LockMasterImpl::CurrentLockMasterImpl(); - if (impl) { - impl->Unlock(false); + void LockMaster::TemporaryUnlock::DestructorImpl() { + impl->Lock(false); } -} -void LockMaster::TemporaryUnlock::DestructorImpl() { - impl->Lock(false); } diff --git a/generate/templates/manual/src/nodegit_wrapper.cc b/generate/templates/manual/src/nodegit_wrapper.cc index 69aed4f94..a9f0483b3 100644 --- a/generate/templates/manual/src/nodegit_wrapper.cc +++ b/generate/templates/manual/src/nodegit_wrapper.cc @@ -79,9 +79,13 @@ template v8::Local NodeGitWrapper::New(const typename Traits::cType *raw, bool selfFreeing, v8::Local owner) { Nan::EscapableHandleScope scope; Local argv[3] = { Nan::New((void *)raw), Nan::New(selfFreeing), owner }; + nodegit::Context *nodegitContext = nodegit::Context::GetCurrentContext(); + Local constructor_template = nodegitContext->GetFromPersistent( + std::string(Traits::className()) + "::Template" + ).As(); return scope.Escape( Nan::NewInstance( - Nan::New(constructor_template), + constructor_template, owner.IsEmpty() ? 2 : 3, // passing an empty handle as part of the arguments causes a crash argv ).ToLocalChecked()); @@ -98,13 +102,10 @@ void NodeGitWrapper::ClearValue() { } template -Nan::Persistent NodeGitWrapper::constructor_template; +thread_local int NodeGitWrapper::SelfFreeingInstanceCount; template -int NodeGitWrapper::SelfFreeingInstanceCount; - -template -int NodeGitWrapper::NonSelfFreeingConstructedCount; +thread_local int NodeGitWrapper::NonSelfFreeingConstructedCount; template NAN_METHOD(NodeGitWrapper::GetSelfFreeingInstanceCount) { diff --git a/generate/templates/manual/src/promise_completion.cc b/generate/templates/manual/src/promise_completion.cc index 22203b41d..dfc560682 100644 --- a/generate/templates/manual/src/promise_completion.cc +++ b/generate/templates/manual/src/promise_completion.cc @@ -1,21 +1,30 @@ #include #include "../include/promise_completion.h" -Nan::Persistent PromiseCompletion::newFn; -Nan::Persistent PromiseCompletion::promiseFulfilled; -Nan::Persistent PromiseCompletion::promiseRejected; - // initializes the persistent handles for NAN_METHODs -void PromiseCompletion::InitializeComponent() { - v8::Local newTemplate = Nan::New(New); +void PromiseCompletion::InitializeComponent(nodegit::Context *nodegitContext) { + Nan::HandleScope scope; + v8::Local nodegitExternal = Nan::New(nodegitContext); + v8::Local newTemplate = Nan::New(New, nodegitExternal); newTemplate->InstanceTemplate()->SetInternalFieldCount(1); - newFn.Reset(Nan::GetFunction(newTemplate).ToLocalChecked()); - promiseFulfilled.Reset(Nan::GetFunction(Nan::New(PromiseFulfilled)).ToLocalChecked()); - promiseRejected.Reset(Nan::GetFunction(Nan::New(PromiseRejected)).ToLocalChecked()); + nodegitContext->SaveToPersistent( + "PromiseCompletion::Template", + Nan::GetFunction(newTemplate).ToLocalChecked() + ); + + v8::Local promiseFulfilled = Nan::GetFunction( + Nan::New(PromiseFulfilled, nodegitExternal) + ).ToLocalChecked(); + nodegitContext->SaveToPersistent("PromiseCompletion::PromiseFulfilled", promiseFulfilled); + + v8::Local promiseRejected = Nan::GetFunction( + Nan::New(PromiseRejected, nodegitExternal) + ).ToLocalChecked(); + nodegitContext->SaveToPersistent("PromiseCompletion::PromiseRejected", promiseRejected); } -bool PromiseCompletion::ForwardIfPromise(v8::Local result, AsyncBaton *baton, Callback callback) +bool PromiseCompletion::ForwardIfPromise(v8::Local result, nodegit::AsyncBaton *baton, Callback callback) { Nan::HandleScope scope; @@ -28,7 +37,10 @@ bool PromiseCompletion::ForwardIfPromise(v8::Local result, AsyncBaton // we can be reasonably certain that the result is a promise // create a new v8 instance of PromiseCompletion - v8::Local object = Nan::NewInstance(Nan::New(newFn)).ToLocalChecked(); + nodegit::Context *nodegitContext = nodegit::Context::GetCurrentContext(); + v8::Local constructor_template = nodegitContext->GetFromPersistent("PromiseCompletion::Template") + .As(); + v8::Local object = Nan::NewInstance(constructor_template).ToLocalChecked(); // set up the native PromiseCompletion object PromiseCompletion *promiseCompletion = ObjectWrap::Unwrap(object); @@ -50,13 +62,18 @@ NAN_METHOD(PromiseCompletion::New) { } // sets up a Promise to forward the promise result via the baton and callback -void PromiseCompletion::Setup(v8::Local thenFn, v8::Local result, AsyncBaton *baton, Callback callback) { +void PromiseCompletion::Setup(v8::Local thenFn, v8::Local result, nodegit::AsyncBaton *baton, Callback callback) { this->callback = callback; this->baton = baton; v8::Local promise = Nan::To(result).ToLocalChecked(); + nodegit::Context *nodegitContext = nodegit::Context::GetCurrentContext(); v8::Local thisHandle = handle(); + v8::Local promiseFulfilled = nodegitContext->GetFromPersistent("PromiseCompletion::PromiseFulfilled") + .As(); + v8::Local promiseRejected = nodegitContext->GetFromPersistent("PromiseCompletion::PromiseRejected") + .As(); v8::Local argv[2] = { Bind(promiseFulfilled, thisHandle), @@ -69,16 +86,16 @@ void PromiseCompletion::Setup(v8::Local thenFn, v8::Local PromiseCompletion::Bind(Nan::Persistent &function, v8::Local object) { +v8::Local PromiseCompletion::Bind(v8::Local function, v8::Local object) { Nan::EscapableHandleScope scope; v8::Local bind = - Nan::Get(Nan::New(function), Nan::New("bind").ToLocalChecked()) + Nan::Get(function, Nan::New("bind").ToLocalChecked()) .ToLocalChecked().As(); v8::Local argv[1] = { object }; - return scope.Escape(Nan::Call(bind, Nan::To(Nan::New(function)).ToLocalChecked(), 1, argv).ToLocalChecked()); + return scope.Escape(Nan::Call(bind, Nan::To(function).ToLocalChecked(), 1, argv).ToLocalChecked()); } // calls the callback stored in the PromiseCompletion, passing the baton that diff --git a/generate/templates/manual/src/reference_counter.cc b/generate/templates/manual/src/reference_counter.cc index 1adc1df4b..e3bc483a7 100644 --- a/generate/templates/manual/src/reference_counter.cc +++ b/generate/templates/manual/src/reference_counter.cc @@ -1,7 +1,7 @@ #include "../include/reference_counter.h" void ReferenceCounter::incrementCountForPointer(void *ptr) { - LockMaster(true, &referenceCountByPointer); + nodegit::LockMaster lm(true, &referenceCountByPointer); if (referenceCountByPointer.find(ptr) == referenceCountByPointer.end()) { referenceCountByPointer[ptr] = 1; } else { @@ -10,7 +10,7 @@ void ReferenceCounter::incrementCountForPointer(void *ptr) { } unsigned long ReferenceCounter::decrementCountForPointer(void *ptr) { - LockMaster(true, &referenceCountByPointer); + nodegit::LockMaster lm(true, &referenceCountByPointer); unsigned long referenceCount = referenceCountByPointer[ptr]; if (referenceCount == 1) { referenceCountByPointer.erase(ptr); diff --git a/generate/templates/manual/src/semaphore.cc b/generate/templates/manual/src/semaphore.cc deleted file mode 100644 index 8185ba774..000000000 --- a/generate/templates/manual/src/semaphore.cc +++ /dev/null @@ -1,17 +0,0 @@ -#include "../include/semaphore.h" - -Semaphore::Semaphore() - : count(0) -{} - -void Semaphore::post() { - std::lock_guard lock(mutex); - ++count; - condition.notify_one(); -} - -void Semaphore::wait() { - std::unique_lock lock(mutex); - while (!count) condition.wait(lock); - --count; -} diff --git a/generate/templates/manual/src/thread_pool.cc b/generate/templates/manual/src/thread_pool.cc index c7a716e7f..771e95074 100644 --- a/generate/templates/manual/src/thread_pool.cc +++ b/generate/templates/manual/src/thread_pool.cc @@ -1,103 +1,581 @@ #include #include "../include/thread_pool.h" -ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop) -{ - uv_async_init(loop, &loopAsync, RunLoopCallbacks); - loopAsync.data = this; - uv_unref((uv_handle_t *)&loopAsync); +#include +#include +#include +#include +#include - workInProgressCount = 0; +#include - for (int i=0; i } -void ThreadPool::QueueWork(Callback workCallback, Callback completionCallback, void *data) { - { - std::lock_guard lock(workMutex); - // there is work on the thread pool - reference the handle so - // node doesn't terminate - uv_ref((uv_handle_t *)&loopAsync); - workQueue.push(Work(workCallback, completionCallback, data)); - workInProgressCount++; +using namespace std::placeholders; + +namespace nodegit { + class Executor { + public: + struct Task { + enum Type { SHUTDOWN, WORK }; + + Task(Type initType) + : type(initType) + {} + + // We must define a virtual destructor so that derived classes are castable + virtual ~Task() {} + + Type type; + }; + + struct ShutdownTask : Task { + ShutdownTask() + : Task(SHUTDOWN) + {} + }; + + struct WorkTask : Task { + WorkTask(ThreadPool::Callback initCallback, Nan::AsyncResource *asyncResource) + : Task(WORK), asyncResource(asyncResource), callback(initCallback) + {} + + Nan::AsyncResource *asyncResource; + ThreadPool::Callback callback; + }; + + typedef std::function PostCallbackEventToOrchestratorFn; + typedef std::function PostCompletedEventToOrchestratorFn; + typedef std::function()> TakeNextTaskFn; + + struct Event { + enum Type { COMPLETED, CALLBACK_TYPE }; + Event(Type initType) + : type(initType) + {} + + Type type; + + // We must define a virtual destructor so that derived classes are castable + virtual ~Event() {} + }; + + struct CompletedEvent : Event { + CompletedEvent() + : Event(COMPLETED) + {} + }; + + struct CallbackEvent : Event { + CallbackEvent(ThreadPool::OnPostCallbackFn initCallback) + : Event(CALLBACK_TYPE), callback(initCallback) + {} + + ThreadPool::Callback operator()(ThreadPool::QueueCallbackFn queueCb, ThreadPool::Callback completedCb) { + return callback(queueCb, completedCb); + } + + private: + ThreadPool::OnPostCallbackFn callback; + }; + + Executor( + PostCallbackEventToOrchestratorFn postCallbackEventToOrchestrator, + PostCompletedEventToOrchestratorFn postCompletedEventToOrchestrator, + TakeNextTaskFn takeNextTask + ); + + void RunTaskLoop(); + + // Orchestrator needs to call this to ensure that the executor is done reading from + // the Orchestrator's memory + void WaitForThreadClose(); + + static Nan::AsyncResource *GetCurrentAsyncResource(); + + static void PostCallbackEvent(ThreadPool::OnPostCallbackFn onPostCallback); + + // Libgit2 will call this before it spawns a child thread. + // That way we can decide what the TLS for that thread should be + // We will make sure that the context for the current async work + // is preserved on the child thread through this method + static void *RetrieveTLSForLibgit2ChildThread(); + + // Libgit2 will call this on a child thread with the pointer that was + // retrieved from RetrieveTLSForLibgit2ChildThread. That allows us + // to store the necessary thread local storage for the child thread + static void SetTLSForLibgit2ChildThread(void *vexecutor); + + // Called when a libgit2 child thread exits. This gives us the ability + // to teardown any TLS we set up for the child thread if we need to + static void TeardownTLSOnLibgit2ChildThread(); + + private: + Nan::AsyncResource *currentAsyncResource; + // We need to populate the executor on every thread that libgit2 + // could make a callback on so that it can correctly queue callbacks + // in the correct javascript context + thread_local static Executor *executor; + PostCallbackEventToOrchestratorFn postCallbackEventToOrchestrator; + PostCompletedEventToOrchestratorFn postCompletedEventToOrchestrator; + TakeNextTaskFn takeNextTask; + std::thread thread; + }; + + Executor::Executor( + PostCallbackEventToOrchestratorFn postCallbackEventToOrchestrator, + PostCompletedEventToOrchestratorFn postCompletedEventToOrchestrator, + TakeNextTaskFn takeNextTask + ) + : currentAsyncResource(nullptr), + postCallbackEventToOrchestrator(postCallbackEventToOrchestrator), + postCompletedEventToOrchestrator(postCompletedEventToOrchestrator), + takeNextTask(takeNextTask), + thread(&Executor::RunTaskLoop, this) + {} + + void Executor::RunTaskLoop() { + // Set the thread local storage so that libgit2 can pick up the current executor + // for the thread. + executor = this; + + for ( ; ; ) { + std::unique_ptr task = takeNextTask(); + if (task->type == Task::Type::SHUTDOWN) { + return; + } + + WorkTask *workTask = static_cast(task.get()); + + currentAsyncResource = workTask->asyncResource; + workTask->callback(); + currentAsyncResource = nullptr; + + postCompletedEventToOrchestrator(); + } } - workSemaphore.post(); -} -void ThreadPool::QueueLoopCallback(Callback callback, void *data, bool isWork) { - // push the callback into the queue - std::lock_guard lock(loopMutex); - LoopCallback loopCallback(callback, data, isWork); - bool queueWasEmpty = loopQueue.empty(); - loopQueue.push(loopCallback); - // we only trigger RunLoopCallbacks via the loopAsync handle if the queue - // was empty. Otherwise, we depend on RunLoopCallbacks to re-trigger itself - if (queueWasEmpty) { - uv_async_send(&loopAsync); + void Executor::WaitForThreadClose() { + thread.join(); } -} -void ThreadPool::ExecuteReverseCallback(Callback reverseCallback, void *data) { - QueueLoopCallback(reverseCallback, data, false); -} + Nan::AsyncResource *Executor::GetCurrentAsyncResource() { + if (executor) { + return executor->currentAsyncResource; + } + + // NOTE this should always be set when a libgit2 callback is running, + // so this case should not happen. + return nullptr; + } -void ThreadPool::RunEventQueue() { - for ( ; ; ) { - // wait until there is work to do - workSemaphore.wait(); - Work work; - { - std::lock_guard lock(workMutex); - // the semaphore should guarantee that queue is not empty - work = workQueue.front(); - workQueue.pop(); + void Executor::PostCallbackEvent(ThreadPool::OnPostCallbackFn onPostCallback) { + if (executor) { + executor->postCallbackEventToOrchestrator(onPostCallback); } + } - // perform the queued work - (*work.workCallback)(work.data); + void *Executor::RetrieveTLSForLibgit2ChildThread() { + return Executor::executor; + } - // schedule the completion callback on the loop - QueueLoopCallback(work.completionCallback, work.data, true); + void Executor::SetTLSForLibgit2ChildThread(void *vexecutor) { + Executor::executor = static_cast(vexecutor); } -} -void ThreadPool::RunLoopCallbacks(uv_async_t* handle) { - static_cast(handle->data)->RunLoopCallbacks(); -} + void Executor::TeardownTLSOnLibgit2ChildThread() { + Executor::executor = nullptr; + } -void ThreadPool::RunLoopCallbacks() { - Nan::HandleScope scope; - v8::Local context = Nan::GetCurrentContext(); - node::CallbackScope callbackScope(context->GetIsolate(), Nan::New(), {0, 0}); - LoopCallback loopCallback; - { - std::lock_guard lock(loopMutex); - // get the next callback to run - loopCallback = loopQueue.front(); + thread_local Executor *Executor::executor = nullptr; + + class Orchestrator { + public: + struct Job { + enum Type { SHUTDOWN, ASYNC_WORK }; + Job(Type initType) + : type(initType) + {} + + virtual ~Job() {} + + Type type; + }; + + struct ShutdownJob : Job { + ShutdownJob() + : Job(SHUTDOWN) + {} + }; + + struct AsyncWorkJob : Job { + AsyncWorkJob(nodegit::AsyncWorker *initWorker) + : Job(ASYNC_WORK), worker(initWorker) + {} + + nodegit::AsyncWorker *worker; + }; + + typedef std::function QueueCallbackOnJSThreadFn; + typedef std::function()> TakeNextJobFn; + + private: + class OrchestratorImpl { + public: + OrchestratorImpl( + QueueCallbackOnJSThreadFn queueCallbackOnJSThread, + TakeNextJobFn takeNextJob + ); + + void RunJobLoop(); + + // The Executor will call this method to queue a CallbackEvent in Orchestrator's event loop + void PostCallbackEvent(ThreadPool::OnPostCallbackFn onPostCallback); + + // The Executor will call this method after completion its work. This should queue + // a CompletedEvent in Thread's event loop + void PostCompletedEvent(); + + // This will be used by Executor to take jobs that the Thread has picked up and run them. + std::unique_ptr TakeNextTask(); + + // This is used to wait for the Orchestrator's thread to shutdown after signaling shutdown + void WaitForThreadClose(); + + private: + // The only thread safe way to pull events from executorEventsQueue + std::shared_ptr TakeEventFromExecutor(); + + void ScheduleWorkTaskOnExecutor(ThreadPool::Callback callback, Nan::AsyncResource *asyncResource); + + void ScheduleShutdownTaskOnExecutor(); + + std::condition_variable taskCondition; + std::unique_ptr taskMutex; + + std::queue> executorEventsQueue; + std::unique_ptr executorEventsMutex; + std::condition_variable executorEventsCondition; + + QueueCallbackOnJSThreadFn queueCallbackOnJSThread; + TakeNextJobFn takeNextJob; + std::unique_ptr task; + std::thread thread; + Executor executor; + + }; + + std::unique_ptr impl; + + public: + Orchestrator( + QueueCallbackOnJSThreadFn queueCallbackOnJSThread, + TakeNextJobFn takeNextJob + ); + + void WaitForThreadClose(); + }; + + Orchestrator::OrchestratorImpl::OrchestratorImpl( + QueueCallbackOnJSThreadFn queueCallbackOnJSThread, + TakeNextJobFn takeNextJob + ) + : taskMutex(new std::mutex), + executorEventsMutex(new std::mutex), + queueCallbackOnJSThread(queueCallbackOnJSThread), + takeNextJob(takeNextJob), + task(nullptr), + thread(&Orchestrator::OrchestratorImpl::RunJobLoop, this), + executor( + std::bind(&Orchestrator::OrchestratorImpl::PostCallbackEvent, this, _1), + std::bind(&Orchestrator::OrchestratorImpl::PostCompletedEvent, this), + std::bind(&Orchestrator::OrchestratorImpl::TakeNextTask, this) + ) + {} + + void Orchestrator::OrchestratorImpl::RunJobLoop() { + for ( ; ; ) { + auto job = takeNextJob(); // takes jobs from the threadpool queue + switch (job->type) { + case Job::Type::SHUTDOWN: { + ScheduleShutdownTaskOnExecutor(); + executor.WaitForThreadClose(); + return; + } + + case Job::Type::ASYNC_WORK: { + std::shared_ptr asyncWorkJob = std::static_pointer_cast(job); + nodegit::AsyncWorker *worker = asyncWorkJob->worker; + // We lock at this level, because we temporarily unlock the lock master + // when a callback is fired. We need to be on the same thread to ensure + // the same thread that acquired the locks also releases them + nodegit::LockMaster lock = worker->AcquireLocks(); + ScheduleWorkTaskOnExecutor(std::bind(&nodegit::AsyncWorker::Execute, worker), worker->GetAsyncResource()); + for ( ; ; ) { + std::shared_ptr event = TakeEventFromExecutor(); + if (event->type == Executor::Event::Type::COMPLETED) { + break; + } + + // We must have received a callback from libgit2 + auto callbackEvent = std::static_pointer_cast(event); + std::shared_ptr callbackMutex(new std::mutex); + std::shared_ptr callbackCondition(new std::condition_variable); + bool hasCompleted = false; + + LockMaster::TemporaryUnlock temporaryUnlock; + auto onCompletedCallback = (*callbackEvent)( + [this](ThreadPool::Callback callback) { + queueCallbackOnJSThread(callback, false); + }, + [callbackCondition, callbackMutex, &hasCompleted]() { + std::lock_guard lock(*callbackMutex); + hasCompleted = true; + callbackCondition->notify_one(); + } + ); + + std::unique_lock lock(*callbackMutex); + while (!hasCompleted) callbackCondition->wait(lock); + onCompletedCallback(); + } + + queueCallbackOnJSThread( + [worker]() { + worker->WorkComplete(); + worker->Destroy(); + }, + true + ); + } + } + } + } + + void Orchestrator::OrchestratorImpl::PostCallbackEvent(ThreadPool::OnPostCallbackFn onPostCallback) { + std::lock_guard lock(*executorEventsMutex); + std::shared_ptr callbackEvent(new Executor::CallbackEvent(onPostCallback)); + executorEventsQueue.push(callbackEvent); + executorEventsCondition.notify_one(); + } + + void Orchestrator::OrchestratorImpl::PostCompletedEvent() { + std::lock_guard lock(*executorEventsMutex); + std::shared_ptr completedEvent(new Executor::CompletedEvent); + executorEventsQueue.push(completedEvent); + executorEventsCondition.notify_one(); + } + + std::shared_ptr Orchestrator::OrchestratorImpl::TakeEventFromExecutor() { + std::unique_lock lock(*executorEventsMutex); + while (executorEventsQueue.empty()) executorEventsCondition.wait(lock); + std::shared_ptr executorEvent = executorEventsQueue.front(); + executorEventsQueue.pop(); + return executorEvent; + } + + void Orchestrator::OrchestratorImpl::ScheduleShutdownTaskOnExecutor() { + std::lock_guard lock(*taskMutex); + task.reset(new Executor::ShutdownTask); + taskCondition.notify_one(); + } + + void Orchestrator::OrchestratorImpl::ScheduleWorkTaskOnExecutor(ThreadPool::Callback callback, Nan::AsyncResource *asyncResource) { + std::lock_guard lock(*taskMutex); + task.reset(new Executor::WorkTask(callback, asyncResource)); + taskCondition.notify_one(); + } + + std::unique_ptr Orchestrator::OrchestratorImpl::TakeNextTask() { + std::unique_lock lock(*taskMutex); + while (!task) taskCondition.wait(lock); + return std::move(task); + } + + void Orchestrator::OrchestratorImpl::WaitForThreadClose() { + thread.join(); } - // perform the queued loop callback - (*loopCallback.callback)(loopCallback.data); + Orchestrator::Orchestrator( + QueueCallbackOnJSThreadFn queueCallbackOnJSThread, + TakeNextJobFn takeNextJob + ) + : impl(new OrchestratorImpl(queueCallbackOnJSThread, takeNextJob)) + {} + + void Orchestrator::WaitForThreadClose() { + impl->WaitForThreadClose(); + } + + class ThreadPoolImpl { + public: + ThreadPoolImpl(int numberOfThreads, uv_loop_t *loop); + + void QueueWorker(nodegit::AsyncWorker *worker); + + std::shared_ptr TakeNextJob(); + + void QueueCallbackOnJSThread(ThreadPool::Callback callback, bool isWork); + + static void RunJSThreadCallbacksFromOrchestrator(uv_async_t *handle); + + void RunJSThreadCallbacksFromOrchestrator(); + + static void RunLoopCallbacks(uv_async_t *handle); + + private: + struct JSThreadCallback { + JSThreadCallback(ThreadPool::Callback callback, bool isWork) + : isWork(isWork), callback(callback) + {} + + JSThreadCallback() + : isWork(false), callback(nullptr) + {} + + void operator()() { + callback(); + } + + bool isWork; + + private: + ThreadPool::Callback callback; + }; + + void RunLoopCallbacks(); - // pop the queue, and if necessary, re-trigger RunLoopCallbacks + std::queue> orchestratorJobQueue; + std::unique_ptr orchestratorJobMutex; + std::condition_variable orchestratorJobCondition; + size_t workInProgressCount; + + // completion and async callbacks to be performed on the loop + std::queue jsThreadCallbackQueue; + std::unique_ptr jsThreadCallbackMutex; + uv_async_t jsThreadCallbackAsync; + + std::vector orchestrators; + }; + + ThreadPoolImpl::ThreadPoolImpl(int numberOfThreads, uv_loop_t *loop) + : orchestratorJobMutex(new std::mutex), + jsThreadCallbackMutex(new std::mutex) { - std::lock_guard lock(loopMutex); - loopQueue.pop(); - if (!loopQueue.empty()) { - uv_async_send(&loopAsync); + uv_async_init(loop, &jsThreadCallbackAsync, RunLoopCallbacks); + jsThreadCallbackAsync.data = this; + uv_unref((uv_handle_t *)&jsThreadCallbackAsync); + + workInProgressCount = 0; + + for (int i = 0; i < numberOfThreads; i++) { + orchestrators.emplace_back( + std::bind(&ThreadPoolImpl::QueueCallbackOnJSThread, this, _1, _2), + std::bind(&ThreadPoolImpl::TakeNextJob, this) + ); + } + } + + void ThreadPoolImpl::QueueWorker(nodegit::AsyncWorker *worker) { + std::lock_guard lock(*orchestratorJobMutex); + // there is work on the thread pool - reference the handle so + // node doesn't terminate + uv_ref((uv_handle_t *)&jsThreadCallbackAsync); + std::shared_ptr job(new Orchestrator::AsyncWorkJob(worker)); + orchestratorJobQueue.emplace(job); + workInProgressCount++; + orchestratorJobCondition.notify_one(); + } + + std::shared_ptr ThreadPoolImpl::TakeNextJob() { + std::unique_lock lock(*orchestratorJobMutex); + while (orchestratorJobQueue.empty()) orchestratorJobCondition.wait(lock); + auto orchestratorJob = orchestratorJobQueue.front(); + + // When the thread pool is shutting down, the thread pool will drain the work queue and replace it with + // a single shared_ptr to a shutdown job, so don't pop the queue when we're shutting down so + // everyone gets the signal + if (orchestratorJob->type != Orchestrator::Job::Type::SHUTDOWN) { + orchestratorJobQueue.pop(); + } + + return orchestratorJob; + } + + void ThreadPoolImpl::QueueCallbackOnJSThread(ThreadPool::Callback callback, bool isWork) { + // push the callback into the queue + std::lock_guard lock(*jsThreadCallbackMutex); + bool queueWasEmpty = jsThreadCallbackQueue.empty(); + jsThreadCallbackQueue.emplace(callback, isWork); + // we only trigger RunLoopCallbacks via the jsThreadCallbackAsync handle if the queue + // was empty. Otherwise, we depend on RunLoopCallbacks to re-trigger itself + if (queueWasEmpty) { + uv_async_send(&jsThreadCallbackAsync); } } - // if there is no ongoing work / completion processing, node doesn't need - // to be prevented from terminating - if (loopCallback.isWork) { - std::lock_guard lock(workMutex); - workInProgressCount --; - if(!workInProgressCount) { - uv_unref((uv_handle_t *)&loopAsync); + void ThreadPoolImpl::RunLoopCallbacks(uv_async_t* handle) { + static_cast(handle->data)->RunLoopCallbacks(); + } + + // TODO every time this is called, if there is a cleanup operation in progress + // for the current context and there is workInProgress, we should ping the + // cleanup async handle which is not created yet + void ThreadPoolImpl::RunLoopCallbacks() { + Nan::HandleScope scope; + v8::Local context = Nan::GetCurrentContext(); + node::CallbackScope callbackScope(context->GetIsolate(), Nan::New(), {0, 0}); + + std::unique_lock lock(*jsThreadCallbackMutex); + // get the next callback to run + JSThreadCallback jsThreadCallback = jsThreadCallbackQueue.front(); + + lock.unlock(); + jsThreadCallback(); + lock.lock(); + + // pop the queue, and if necessary, re-trigger RunLoopCallbacks + jsThreadCallbackQueue.pop(); + if (!jsThreadCallbackQueue.empty()) { + uv_async_send(&jsThreadCallbackAsync); + } + + // if there is no ongoing work / completion processing, node doesn't need + // to be prevented from terminating + if (jsThreadCallback.isWork) { + std::lock_guard orchestratorLock(*orchestratorJobMutex); + workInProgressCount--; + if (!workInProgressCount) { + uv_unref((uv_handle_t *)&jsThreadCallbackAsync); + } } } + + ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop) + : impl(new ThreadPoolImpl(numberOfThreads, loop)) + {} + + ThreadPool::~ThreadPool() {} + + void ThreadPool::QueueWorker(nodegit::AsyncWorker *worker) { + impl->QueueWorker(worker); + } + + void ThreadPool::PostCallbackEvent(OnPostCallbackFn onPostCallback) { + Executor::PostCallbackEvent(onPostCallback); + } + + Nan::AsyncResource *ThreadPool::GetCurrentAsyncResource() { + return Executor::GetCurrentAsyncResource(); + } + + void ThreadPool::InitializeGlobal() { + git_custom_tls_set_callbacks( + Executor::RetrieveTLSForLibgit2ChildThread, + Executor::SetTLSForLibgit2ChildThread, + Executor::TeardownTLSOnLibgit2ChildThread + ); + } } diff --git a/generate/templates/manual/src/wrapper.cc b/generate/templates/manual/src/wrapper.cc index ffd9bc584..9daae7848 100644 --- a/generate/templates/manual/src/wrapper.cc +++ b/generate/templates/manual/src/wrapper.cc @@ -16,18 +16,20 @@ Wrapper::Wrapper(void *raw) { this->raw = raw; } -void Wrapper::InitializeComponent(Local target) { +void Wrapper::InitializeComponent(Local target, nodegit::Context *nodegitContext) { Nan::HandleScope scope; - Local tpl = Nan::New(JSNewFunction); + Local nodegitExternal = Nan::New(nodegitContext); + Local tpl = Nan::New(JSNewFunction, nodegitExternal); tpl->InstanceTemplate()->SetInternalFieldCount(1); tpl->SetClassName(Nan::New("Wrapper").ToLocalChecked()); - Nan::SetPrototypeMethod(tpl, "toBuffer", ToBuffer); + Nan::SetPrototypeMethod(tpl, "toBuffer", ToBuffer, nodegitExternal); - constructor_template.Reset(tpl); - Nan::Set(target, Nan::New("Wrapper").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked()); + Local constructor_template = Nan::GetFunction(tpl).ToLocalChecked(); + nodegitContext->SaveToPersistent("Wrapper::Template", constructor_template); + Nan::Set(target, Nan::New("Wrapper").ToLocalChecked(), constructor_template); } NAN_METHOD(Wrapper::JSNewFunction) { @@ -47,8 +49,9 @@ Local Wrapper::New(const void *raw) { Local argv[1] = { Nan::New((void *)raw) }; Local instance; - Local constructorHandle = Nan::New(constructor_template); - instance = Nan::NewInstance(Nan::GetFunction(constructorHandle).ToLocalChecked(), 1, argv).ToLocalChecked(); + nodegit::Context *nodegitContext = nodegit::Context::GetCurrentContext(); + Local constructor_template = nodegitContext->GetFromPersistent("Wrapper::Template").As(); + instance = Nan::NewInstance(constructor_template, 1, argv).ToLocalChecked(); return scope.Escape(instance); } @@ -75,6 +78,3 @@ NAN_METHOD(Wrapper::ToBuffer) { info.GetReturnValue().Set(nodeBuffer); } - - -Nan::Persistent Wrapper::constructor_template; diff --git a/generate/templates/partials/async_function.cc b/generate/templates/partials/async_function.cc index 9a1ce26fe..b0ad8ab4b 100644 --- a/generate/templates/partials/async_function.cc +++ b/generate/templates/partials/async_function.cc @@ -74,25 +74,29 @@ NAN_METHOD({{ cppClassName }}::{{ cppFunctionName }}) { {%endif%} {%endeach%} - AsyncLibgit2QueueWorker(worker); + nodegit::Context *nodegitContext = reinterpret_cast(info.Data().As()->Value()); + nodegitContext->QueueWorker(worker); return; } +nodegit::LockMaster {{ cppClassName }}::{{ cppFunctionName }}Worker::AcquireLocks() { + nodegit::LockMaster lockMaster( + /*asyncAction: */true + {%each args|argsInfo as arg %} + {%if arg.cType|isPointer%} + {%if not arg.cType|isDoublePointer%} + ,baton->{{ arg.name }} + {%endif%} + {%endif%} + {%endeach%} + ); + + return lockMaster; +} + void {{ cppClassName }}::{{ cppFunctionName }}Worker::Execute() { git_error_clear(); - { - LockMaster lockMaster( - /*asyncAction: */true - {%each args|argsInfo as arg %} - {%if arg.cType|isPointer%} - {%if not arg.cType|isDoublePointer%} - ,baton->{{ arg.name }} - {%endif%} - {%endif%} - {%endeach%} - ); - {%if .|hasReturnType %} {{ return.cType }} result = {{ cFunctionName }}( {%else%} @@ -123,7 +127,6 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::Execute() { baton->result = result; {%endif%} - } } void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleOKCallback() { diff --git a/generate/templates/partials/callback_helpers.cc b/generate/templates/partials/callback_helpers.cc index c3810371c..8bbc2ecd0 100644 --- a/generate/templates/partials/callback_helpers.cc +++ b/generate/templates/partials/callback_helpers.cc @@ -49,8 +49,8 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(void }; Nan::TryCatch tryCatch; - // TODO This should take an async_resource, but we will need to figure out how to pipe the correct context into this - Nan::MaybeLocal maybeResult = Nan::Call(*callback, {{ cbFunction.args|callbackArgsCount }}, argv); + Nan::MaybeLocal maybeResult = (*callback)(baton->GetAsyncResource(), {{ cbFunction.args|callbackArgsCount }}, argv); + v8::Local result; if (!maybeResult.IsEmpty()) { result = maybeResult.ToLocalChecked(); @@ -88,7 +88,7 @@ void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(void baton->Done(); } -void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local result) { +void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_promiseCompleted(bool isFulfilled, nodegit::AsyncBaton *_baton, v8::Local result) { Nan::HandleScope scope; {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(_baton); diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index 35b583bdc..0b0dec1ee 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -144,7 +144,7 @@ baton->ExecuteAsync({{ field.name }}_async); delete baton; } else { - baton->ExecuteAsync({{ field.name }}_async, deleteBaton); + baton->ExecuteAsync({{ field.name }}_async, nodegit::deleteBaton); } return; {% else %} @@ -158,7 +158,7 @@ delete baton; } else { result = baton->defaultResult; - baton->ExecuteAsync({{ field.name }}_async, deleteBaton); + baton->ExecuteAsync({{ field.name }}_async, nodegit::deleteBaton); } return result; {% endif %} @@ -205,8 +205,11 @@ Nan::TryCatch tryCatch; - // TODO This should take an async_resource, but we will need to figure out how to pipe the correct context into this - Nan::MaybeLocal maybeResult = Nan::Call(*(instance->{{ field.name }}.GetCallback()), {{ field.args|callbackArgsCount }}, argv); + Nan::MaybeLocal maybeResult = (*(instance->{{ field.name }}.GetCallback()))( + baton->GetAsyncResource(), + {{ field.args|callbackArgsCount }}, + argv + ); v8::Local result; if (!maybeResult.IsEmpty()) { result = maybeResult.ToLocalChecked(); @@ -247,7 +250,7 @@ {% endif %} } - void {{ cppClassName }}::{{ field.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local result) { + void {{ cppClassName }}::{{ field.name }}_promiseCompleted(bool isFulfilled, nodegit::AsyncBaton *_baton, v8::Local result) { Nan::HandleScope scope; {{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(_baton); diff --git a/generate/templates/partials/sync_function.cc b/generate/templates/partials/sync_function.cc index 4a76463ed..3e6b6cc6a 100644 --- a/generate/templates/partials/sync_function.cc +++ b/generate/templates/partials/sync_function.cc @@ -34,7 +34,7 @@ NAN_METHOD({{ cppClassName }}::{{ cppFunctionName }}) { git_error_clear(); { // lock master scope start - LockMaster lockMaster( + nodegit::LockMaster lockMaster( /*asyncAction: */false {%each args|argsInfo as arg %} {%if arg.cType|isPointer%} diff --git a/generate/templates/partials/traits.h b/generate/templates/partials/traits.h index 3e63e42e8..c708c965b 100644 --- a/generate/templates/partials/traits.h +++ b/generate/templates/partials/traits.h @@ -17,6 +17,7 @@ struct {{ cppClassName }}Traits { {% endif %} } + static std::string className() { return "{{ cppClassName }}"; }; static const bool isSingleton = {{ isSingleton | toBool }}; static const bool isFreeable = {{ freeFunctionName | toBool}}; static void free({{ cType }} *raw) { diff --git a/generate/templates/templates/binding.gyp b/generate/templates/templates/binding.gyp index 52620e4bb..2f9acf366 100644 --- a/generate/templates/templates/binding.gyp +++ b/generate/templates/templates/binding.gyp @@ -47,8 +47,10 @@ }, "sources": [ "src/async_baton.cc", + "src/async_worker.cc", "src/lock_master.cc", "src/reference_counter.cc", + "src/thread_pool.cc", "src/nodegit.cc", "src/init_ssh2.cc", "src/promise_completion.cc", @@ -59,9 +61,8 @@ "src/convenient_hunk.cc", "src/filter_registry.cc", "src/git_buf_converter.cc", - "src/semaphore.cc", "src/str_array_converter.cc", - "src/thread_pool.cc", + "src/context.cc", {% each %} {% if type != "enum" %} "src/{{ name }}.cc", @@ -113,7 +114,7 @@ "GCC_ENABLE_CPP_EXCEPTIONS": "YES", "MACOSX_DEPLOYMENT_TARGET": "10.9", 'CLANG_CXX_LIBRARY': 'libc++', - 'CLANG_CXX_LANGUAGE_STANDARD':'c++11', + 'CLANG_CXX_LANGUAGE_STANDARD':'c++14', "WARNING_CFLAGS": [ "-Wno-unused-variable", @@ -165,7 +166,7 @@ [ "OS=='linux' or OS.endswith('bsd') or <(is_IBMi) == 1", { "cflags": [ - "-std=c++11" + "-std=c++14" ] } ], diff --git a/generate/templates/templates/class_content.cc b/generate/templates/templates/class_content.cc index dbac5e097..275e4f8b0 100644 --- a/generate/templates/templates/class_content.cc +++ b/generate/templates/templates/class_content.cc @@ -13,7 +13,6 @@ extern "C" { #include "../include/functions/copy.h" #include "../include/{{ filename }}.h" #include "nodegit_wrapper.cc" -#include "../include/async_libgit2_queue_worker.h" {% each dependencies as dependency %} #include "{{ dependency }}" @@ -43,10 +42,11 @@ using namespace node; {% endeach %} } - void {{ cppClassName }}::InitializeComponent(v8::Local target) { + void {{ cppClassName }}::InitializeComponent(v8::Local target, nodegit::Context *nodegitContext) { Nan::HandleScope scope; - v8::Local tpl = Nan::New(JSNewFunction); + v8::Local nodegitExternal = Nan::New(nodegitContext); + v8::Local tpl = Nan::New(JSNewFunction, nodegitExternal); tpl->InstanceTemplate()->SetInternalFieldCount(1); tpl->SetClassName(Nan::New("{{ jsClassName }}").ToLocalChecked()); @@ -54,40 +54,41 @@ using namespace node; {% each functions as function %} {% if not function.ignore %} {% if function.isPrototypeMethod %} - Nan::SetPrototypeMethod(tpl, "{{ function.jsFunctionName }}", {{ function.cppFunctionName }}); + Nan::SetPrototypeMethod(tpl, "{{ function.jsFunctionName }}", {{ function.cppFunctionName }}, nodegitExternal); {% else %} - Nan::SetMethod(tpl, "{{ function.jsFunctionName }}", {{ function.cppFunctionName }}); + Nan::SetMethod(tpl, "{{ function.jsFunctionName }}", {{ function.cppFunctionName }}, nodegitExternal); {% endif %} {% endif %} {% endeach %} {% each fields as field %} {% if not field.ignore %} - Nan::SetPrototypeMethod(tpl, "{{ field.jsFunctionName }}", {{ field.cppFunctionName }}); + Nan::SetPrototypeMethod(tpl, "{{ field.jsFunctionName }}", {{ field.cppFunctionName }}, nodegitExternal); {% endif %} {% endeach %} InitializeTemplate(tpl); - v8::Local _constructor_template = Nan::GetFunction(tpl).ToLocalChecked(); - constructor_template.Reset(_constructor_template); - Nan::Set(target, Nan::New("{{ jsClassName }}").ToLocalChecked(), _constructor_template); + v8::Local constructor_template = Nan::GetFunction(tpl).ToLocalChecked(); + nodegitContext->SaveToPersistent("{{ cppClassName }}::Template", constructor_template); + Nan::Set(target, Nan::New("{{ jsClassName }}").ToLocalChecked(), constructor_template); } {% else %} - void {{ cppClassName }}::InitializeComponent(v8::Local target) { + void {{ cppClassName }}::InitializeComponent(v8::Local target, nodegit::Context *nodegitContext) { Nan::HandleScope scope; + Local nodegitExternal = Nan::New(nodegitContext); {% if functions|hasFunctionOnRootProto %} - v8::Local object = Nan::New({{ functions|getCPPFunctionForRootProto }}); + v8::Local object = Nan::New({{ functions|getCPPFunctionForRootProto }}, nodegitExternal); {% else %} v8::Local object = Nan::New(); {% endif %} {% each functions as function %} {% if not function.ignore %} - Nan::SetMethod(object, "{{ function.jsFunctionName }}", {{ function.cppFunctionName }}); + Nan::SetMethod(object, "{{ function.jsFunctionName }}", {{ function.cppFunctionName }}, nodegitExternal); {% endif %} {% endeach %} diff --git a/generate/templates/templates/class_header.h b/generate/templates/templates/class_header.h index 746692e9e..ca394278f 100644 --- a/generate/templates/templates/class_header.h +++ b/generate/templates/templates/class_header.h @@ -8,6 +8,9 @@ #include #include "async_baton.h" +#include "async_worker.h" +#include "context.h" +#include "lock_master.h" #include "nodegit_wrapper.h" #include "promise_completion.h" #include "reference_counter.h" @@ -55,7 +58,7 @@ class {{ cppClassName }} : public friend class NodeGitWrapper<{{ cppClassName }}Traits>; {%endif %} public: - static void InitializeComponent (v8::Local target); + static void InitializeComponent (v8::Local target, nodegit::Context *nodegitContext); {% each functions as function %} {% if not function.ignore %} @@ -71,14 +74,15 @@ class {{ cppClassName }} : public ); static void {{ function.cppFunctionName }}_{{ arg.name }}_async(void *baton); - static void {{ function.cppFunctionName }}_{{ arg.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local result); - struct {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton : public AsyncBatonWithResult<{{ arg.return.type }}> { + static void {{ function.cppFunctionName }}_{{ arg.name }}_promiseCompleted(bool isFulfilled, nodegit::AsyncBaton *_baton, v8::Local result); + class {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton : public nodegit::AsyncBatonWithResult<{{ arg.return.type }}> { + public: {% each arg.args|argsInfo as cbArg %} {{ cbArg.cType }} {{ cbArg.name }}; {% endeach %} {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton(const {{ arg.return.type }} &defaultResult) - : AsyncBatonWithResult<{{ arg.return.type }}>(defaultResult) { + : nodegit::AsyncBatonWithResult<{{ arg.return.type }}>(defaultResult) { } }; {% endif %} @@ -104,16 +108,6 @@ class {{ cppClassName }} : public ~{{ cppClassName }}(); {%endif%} - {% each functions as function %} - {% if not function.ignore %} - {% each function.args as arg %} - {% if arg.saveArg %} - Nan::Persistent {{ function.cppFunctionName }}_{{ arg.name }}; - {% endif %} - {% endeach %} - {% endif %} - {% endeach %} - {%each fields as field%} {%if not field.ignore%} static NAN_METHOD({{ field.cppFunctionName }}); @@ -138,16 +132,17 @@ class {{ cppClassName }} : public {%endif%} {%endeach%} }; - class {{ function.cppFunctionName }}Worker : public Nan::AsyncWorker { + class {{ function.cppFunctionName }}Worker : public nodegit::AsyncWorker { public: {{ function.cppFunctionName }}Worker( {{ function.cppFunctionName }}Baton *_baton, Nan::Callback *callback - ) : Nan::AsyncWorker(callback) + ) : nodegit::AsyncWorker(callback, "nodegit:AsyncWorker:{{ cppClassName }}:{{ function.cppFunctionName }}") , baton(_baton) {}; ~{{ function.cppFunctionName }}Worker() {}; void Execute(); void HandleOKCallback(); + nodegit::LockMaster AcquireLocks(); private: {{ function.cppFunctionName }}Baton *baton; diff --git a/generate/templates/templates/nodegit.cc b/generate/templates/templates/nodegit.cc index 5d16fa3bb..e174ae27b 100644 --- a/generate/templates/templates/nodegit.cc +++ b/generate/templates/templates/nodegit.cc @@ -11,6 +11,7 @@ #include "../include/init_ssh2.h" #include "../include/lock_master.h" #include "../include/nodegit.h" +#include "../include/context.h" #include "../include/wrapper.h" #include "../include/promise_completion.h" #include "../include/functions/copy.h" @@ -66,9 +67,6 @@ void OpenSSL_ThreadSetup() { CRYPTO_THREADID_set_callback(OpenSSL_IDCallback); } -// TODO initialize a thread pool per context. Replace uv_default_loop() with node::GetCurrentEventLoop(isolate); -ThreadPool libgit2ThreadPool(10, uv_default_loop()); - static std::once_flag libraryInitializedFlag; static std::mutex libraryInitializationMutex; @@ -84,24 +82,30 @@ NAN_MODULE_INIT(init) { init_ssh2(); // Initialize libgit2. git_libgit2_init(); + + // Register thread pool with libgit2 + nodegit::ThreadPool::InitializeGlobal(); }); } Nan::HandleScope scope; + Local context = Nan::GetCurrentContext(); + Isolate *isolate = context->GetIsolate(); + nodegit::Context *nodegitContext = new nodegit::Context(isolate); - Wrapper::InitializeComponent(target); - PromiseCompletion::InitializeComponent(); + Wrapper::InitializeComponent(target, nodegitContext); + PromiseCompletion::InitializeComponent(nodegitContext); {% each %} {% if type != "enum" %} - {{ cppClassName }}::InitializeComponent(target); + {{ cppClassName }}::InitializeComponent(target, nodegitContext); {% endif %} {% endeach %} - ConvenientHunk::InitializeComponent(target); - ConvenientPatch::InitializeComponent(target); - GitFilterRegistry::InitializeComponent(target); + ConvenientHunk::InitializeComponent(target, nodegitContext); + ConvenientPatch::InitializeComponent(target, nodegitContext); + GitFilterRegistry::InitializeComponent(target, nodegitContext); - LockMaster::InitializeContext(); + nodegit::LockMaster::InitializeContext(); } NODE_MODULE(nodegit, init) diff --git a/generate/templates/templates/struct_content.cc b/generate/templates/templates/struct_content.cc index 0450447ac..a7db84d53 100644 --- a/generate/templates/templates/struct_content.cc +++ b/generate/templates/templates/struct_content.cc @@ -106,10 +106,11 @@ void {{ cppClassName }}::ConstructFields() { {% endeach %} } -void {{ cppClassName }}::InitializeComponent(v8::Local target) { +void {{ cppClassName }}::InitializeComponent(Local target, nodegit::Context *nodegitContext) { Nan::HandleScope scope; - v8::Local tpl = Nan::New(JSNewFunction); + Local nodegitExternal = Nan::New(nodegitContext); + Local tpl = Nan::New(JSNewFunction, nodegitExternal); tpl->InstanceTemplate()->SetInternalFieldCount(1); tpl->SetClassName(Nan::New("{{ jsClassName }}").ToLocalChecked()); @@ -117,16 +118,16 @@ void {{ cppClassName }}::InitializeComponent(v8::Local target) { {% each fields as field %} {% if not field.ignore %} {% if not field | isPayload %} - Nan::SetAccessor(tpl->InstanceTemplate(), Nan::New("{{ field.jsFunctionName }}").ToLocalChecked(), Get{{ field.cppFunctionName}}, Set{{ field.cppFunctionName}}); + Nan::SetAccessor(tpl->InstanceTemplate(), Nan::New("{{ field.jsFunctionName }}").ToLocalChecked(), Get{{ field.cppFunctionName}}, Set{{ field.cppFunctionName}}, nodegitExternal); {% endif %} {% endif %} {% endeach %} InitializeTemplate(tpl); - v8::Local _constructor_template = Nan::GetFunction(tpl).ToLocalChecked(); - constructor_template.Reset(_constructor_template); - Nan::Set(target, Nan::New("{{ jsClassName }}").ToLocalChecked(), _constructor_template); + v8::Local constructor_template = Nan::GetFunction(tpl).ToLocalChecked(); + nodegitContext->SaveToPersistent("{{ cppClassName }}::Template", constructor_template); + Nan::Set(target, Nan::New("{{ jsClassName }}").ToLocalChecked(), constructor_template); } {% partial fieldAccessors . %} diff --git a/generate/templates/templates/struct_header.h b/generate/templates/templates/struct_header.h index 568bcfc91..a80e7a5cf 100644 --- a/generate/templates/templates/struct_header.h +++ b/generate/templates/templates/struct_header.h @@ -7,7 +7,9 @@ #include #include "async_baton.h" +#include "async_worker.h" #include "callback_wrapper.h" +#include "context.h" #include "reference_counter.h" #include "nodegit_wrapper.h" @@ -37,7 +39,7 @@ class {{ cppClassName }} : public NodeGitWrapper<{{ cppClassName }}Traits> { friend class NodeGitWrapper<{{ cppClassName }}Traits>; public: {{ cppClassName }}({{ cType }}* raw, bool selfFreeing, v8::Local owner = v8::Local()); - static void InitializeComponent (v8::Local target); + static void InitializeComponent (v8::Local target, nodegit::Context *nodegitContext); {% each fields as field %} {% if not field.ignore %} @@ -52,25 +54,27 @@ class {{ cppClassName }} : public NodeGitWrapper<{{ cppClassName }}Traits> { ); static void {{ field.name }}_async(void *baton); - static void {{ field.name }}_promiseCompleted(bool isFulfilled, AsyncBaton *_baton, v8::Local result); + static void {{ field.name }}_promiseCompleted(bool isFulfilled, nodegit::AsyncBaton *_baton, v8::Local result); {% if field.return.type == 'void' %} - struct {{ field.name|titleCase }}Baton : public AsyncBatonWithNoResult { + class {{ field.name|titleCase }}Baton : public nodegit::AsyncBatonWithNoResult { + public: {% each field.args|argsInfo as arg %} {{ arg.cType }} {{ arg.name }}; {% endeach %} {{ field.name|titleCase }}Baton() - : AsyncBatonWithNoResult() { + : nodegit::AsyncBatonWithNoResult() { } }; {% else %} - struct {{ field.name|titleCase }}Baton : public AsyncBatonWithResult<{{ field.return.type }}> { + class {{ field.name|titleCase }}Baton : public nodegit::AsyncBatonWithResult<{{ field.return.type }}> { + public: {% each field.args|argsInfo as arg %} {{ arg.cType }} {{ arg.name }}; {% endeach %} {{ field.name|titleCase }}Baton(const {{ field.return.type }} &defaultResult) - : AsyncBatonWithResult<{{ field.return.type }}>(defaultResult) { + : nodegit::AsyncBatonWithResult<{{ field.return.type }}>(defaultResult) { } }; {% endif %} From 42e266ed124b1524acaa693a499c08f506db543d Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Tue, 25 Aug 2020 16:49:31 -0700 Subject: [PATCH 09/40] Skeleton shutdown logic for thread pool and cancellation of work --- .../templates/manual/include/async_baton.h | 10 +- .../templates/manual/include/async_worker.h | 10 ++ generate/templates/manual/include/context.h | 10 +- .../templates/manual/include/thread_pool.h | 6 +- generate/templates/manual/src/async_baton.cc | 13 +- generate/templates/manual/src/async_worker.cc | 7 + generate/templates/manual/src/context.cc | 37 +++-- generate/templates/manual/src/thread_pool.cc | 146 +++++++++++++++--- generate/templates/partials/async_function.cc | 2 + .../templates/partials/callback_helpers.cc | 6 +- .../templates/partials/field_accessors.cc | 11 +- generate/templates/templates/class_header.h | 1 + generate/templates/templates/struct_header.h | 1 + 13 files changed, 205 insertions(+), 55 deletions(-) diff --git a/generate/templates/manual/include/async_baton.h b/generate/templates/manual/include/async_baton.h index b9df1cfec..54f580f94 100644 --- a/generate/templates/manual/include/async_baton.h +++ b/generate/templates/manual/include/async_baton.h @@ -28,7 +28,7 @@ namespace nodegit { Nan::AsyncResource *GetAsyncResource(); protected: - void ExecuteAsyncPerform(AsyncCallback asyncCallback, CompletionCallback onCompletion); + void ExecuteAsyncPerform(AsyncCallback asyncCallback, AsyncCallback asyncCancelCb, CompletionCallback onCompletion); private: void SignalCompletion(); @@ -53,17 +53,17 @@ namespace nodegit { : defaultResult(defaultResult) { } - ResultT ExecuteAsync(AsyncBaton::AsyncCallback asyncCallback, AsyncBaton::CompletionCallback onCompletion = nullptr) { + ResultT ExecuteAsync(AsyncBaton::AsyncCallback asyncCallback, AsyncBaton::AsyncCallback asyncCancelCb, AsyncBaton::CompletionCallback onCompletion = nullptr) { result = 0; - ExecuteAsyncPerform(asyncCallback, onCompletion); + ExecuteAsyncPerform(asyncCallback, asyncCancelCb, onCompletion); return result; } }; class AsyncBatonWithNoResult : public AsyncBaton { public: - void ExecuteAsync(AsyncBaton::AsyncCallback asyncCallback, AsyncBaton::CompletionCallback onCompletion = nullptr) { - ExecuteAsyncPerform(asyncCallback, onCompletion); + void ExecuteAsync(AsyncBaton::AsyncCallback asyncCallback, AsyncBaton::AsyncCallback asyncCancelCb, AsyncBaton::CompletionCallback onCompletion = nullptr) { + ExecuteAsyncPerform(asyncCallback, asyncCancelCb, onCompletion); } }; } diff --git a/generate/templates/manual/include/async_worker.h b/generate/templates/manual/include/async_worker.h index fc10893e6..77159a605 100644 --- a/generate/templates/manual/include/async_worker.h +++ b/generate/templates/manual/include/async_worker.h @@ -9,8 +9,18 @@ namespace nodegit { public: AsyncWorker(Nan::Callback *callback, const char *resourceName); + // This must be implemented by every async worker + // so that the thread pool can lock separately + // from the execute method in the AsyncWorker virtual nodegit::LockMaster AcquireLocks() = 0; + // Ensure that the `HandleErrorCallback` will be called + // when the AsyncWork is complete + void Cancel(); + + // Retrieves the async resource attached to this AsyncWorker + // This is used to inform libgit2 callbacks what asyncResource + // they should use when working with any javascript Nan::AsyncResource *GetAsyncResource(); }; } diff --git a/generate/templates/manual/include/context.h b/generate/templates/manual/include/context.h index 8b2ef66be..1027622ec 100644 --- a/generate/templates/manual/include/context.h +++ b/generate/templates/manual/include/context.h @@ -15,15 +15,17 @@ namespace nodegit { public: Context(v8::Isolate *isolate); - void QueueWorker(nodegit::AsyncWorker *worker); + ~Context(); - void SaveToPersistent(std::string key, const v8::Local &value); + static Context *GetCurrentContext(); v8::Local GetFromPersistent(std::string key); - static Context *GetCurrentContext(); + void QueueWorker(nodegit::AsyncWorker *worker); - ~Context(); + void SaveToPersistent(std::string key, const v8::Local &value); + + void ShutdownThreadPool(); private: v8::Isolate *isolate; diff --git a/generate/templates/manual/include/thread_pool.h b/generate/templates/manual/include/thread_pool.h index 5baf4e743..74ed09bdf 100644 --- a/generate/templates/manual/include/thread_pool.h +++ b/generate/templates/manual/include/thread_pool.h @@ -14,7 +14,7 @@ namespace nodegit { class ThreadPool { public: typedef std::function Callback; - typedef std::function QueueCallbackFn; + typedef std::function QueueCallbackFn; typedef std::function OnPostCallbackFn; // Initializes thread pool and spins up the requested number of threads @@ -41,6 +41,10 @@ namespace nodegit { // Called once at libgit2 initialization to setup contracts with libgit2 static void InitializeGlobal(); + // Will wait for all threads to terminate before returning + // It will also clean up any resources that the thread pool is keeping alive + void Shutdown(); + private: std::unique_ptr impl; }; diff --git a/generate/templates/manual/src/async_baton.cc b/generate/templates/manual/src/async_baton.cc index da99b9e26..2bcd0ea82 100644 --- a/generate/templates/manual/src/async_baton.cc +++ b/generate/templates/manual/src/async_baton.cc @@ -23,10 +23,13 @@ namespace nodegit { return asyncResource; } - void AsyncBaton::ExecuteAsyncPerform(AsyncCallback asyncCallback, CompletionCallback onCompletion) { + void AsyncBaton::ExecuteAsyncPerform(AsyncCallback asyncCallback, AsyncCallback asyncCancelCb, CompletionCallback onCompletion) { auto jsCallback = [asyncCallback, this]() { asyncCallback(this); }; + auto cancelCallback = [asyncCancelCb, this]() { + asyncCancelCb(this); + }; if (onCompletion) { this->onCompletion = [this, onCompletion]() { @@ -34,11 +37,11 @@ namespace nodegit { }; ThreadPool::PostCallbackEvent( - [this, jsCallback]( + [this, jsCallback, cancelCallback]( ThreadPool::QueueCallbackFn queueCallback, ThreadPool::Callback callbackCompleted ) -> ThreadPool::Callback { - queueCallback(jsCallback); + queueCallback(jsCallback, cancelCallback); callbackCompleted(); return []() {}; @@ -46,13 +49,13 @@ namespace nodegit { ); } else { ThreadPool::PostCallbackEvent( - [this, jsCallback]( + [this, jsCallback, cancelCallback]( ThreadPool::QueueCallbackFn queueCallback, ThreadPool::Callback callbackCompleted ) -> ThreadPool::Callback { this->onCompletion = callbackCompleted; - queueCallback(jsCallback); + queueCallback(jsCallback, cancelCallback); return std::bind(&AsyncBaton::SignalCompletion, this); } diff --git a/generate/templates/manual/src/async_worker.cc b/generate/templates/manual/src/async_worker.cc index 65c7a4e1c..bc7162a84 100644 --- a/generate/templates/manual/src/async_worker.cc +++ b/generate/templates/manual/src/async_worker.cc @@ -5,6 +5,13 @@ namespace nodegit { : Nan::AsyncWorker(callback, resourceName) {} + void AsyncWorker::Cancel() { + // We use Nan::AsyncWorker's ErrorMessage flow + // to trigger `HandleErrorCallback` for cancellation + // of AsyncWork + SetErrorMessage("SHUTTING DOWN"); + } + Nan::AsyncResource *AsyncWorker::GetAsyncResource() { return async_resource; } diff --git a/generate/templates/manual/src/context.cc b/generate/templates/manual/src/context.cc index b5454b6e4..16a8e6267 100644 --- a/generate/templates/manual/src/context.cc +++ b/generate/templates/manual/src/context.cc @@ -1,6 +1,16 @@ #include "../include/context.h" namespace nodegit { + std::map Context::contexts; + + static void CleanupContext(void *data) { + Context *context = static_cast(data); + + context->ShutdownThreadPool(); + + delete context; + } + Context::Context(v8::Isolate *isolate) : isolate(isolate), threadPool(10, node::GetCurrentEventLoop(isolate)) { @@ -8,20 +18,18 @@ namespace nodegit { v8::Local storage = Nan::New(); persistentStorage.Reset(storage); contexts[isolate] = this; + node::AddEnvironmentCleanupHook(isolate, CleanupContext, this); } Context::~Context() { contexts.erase(isolate); } - void Context::QueueWorker(nodegit::AsyncWorker *worker) { - threadPool.QueueWorker(worker); - } - - void Context::SaveToPersistent(std::string key, const v8::Local &value) { + Context *Context::GetCurrentContext() { Nan::HandleScope scope; - v8::Local storage = Nan::New(persistentStorage); - Nan::Set(storage, Nan::New(key).ToLocalChecked(), value); + v8::Local context = Nan::GetCurrentContext(); + v8::Isolate *isolate = context->GetIsolate(); + return contexts[isolate]; } v8::Local Context::GetFromPersistent(std::string key) { @@ -31,12 +39,17 @@ namespace nodegit { return scope.Escape(value.ToLocalChecked()); } - Context *Context::GetCurrentContext() { + void Context::QueueWorker(nodegit::AsyncWorker *worker) { + threadPool.QueueWorker(worker); + } + + void Context::SaveToPersistent(std::string key, const v8::Local &value) { Nan::HandleScope scope; - v8::Local context = Nan::GetCurrentContext(); - v8::Isolate *isolate = context->GetIsolate(); - return contexts[isolate]; + v8::Local storage = Nan::New(persistentStorage); + Nan::Set(storage, Nan::New(key).ToLocalChecked(), value); } - std::map Context::contexts; + void Context::ShutdownThreadPool() { + threadPool.Shutdown(); + } } diff --git a/generate/templates/manual/src/thread_pool.cc b/generate/templates/manual/src/thread_pool.cc index 771e95074..4cf5a23f6 100644 --- a/generate/templates/manual/src/thread_pool.cc +++ b/generate/templates/manual/src/thread_pool.cc @@ -218,7 +218,7 @@ namespace nodegit { nodegit::AsyncWorker *worker; }; - typedef std::function QueueCallbackOnJSThreadFn; + typedef std::function QueueCallbackOnJSThreadFn; typedef std::function()> TakeNextJobFn; private: @@ -297,7 +297,7 @@ namespace nodegit { void Orchestrator::OrchestratorImpl::RunJobLoop() { for ( ; ; ) { - auto job = takeNextJob(); // takes jobs from the threadpool queue + auto job = takeNextJob(); switch (job->type) { case Job::Type::SHUTDOWN: { ScheduleShutdownTaskOnExecutor(); @@ -327,8 +327,8 @@ namespace nodegit { LockMaster::TemporaryUnlock temporaryUnlock; auto onCompletedCallback = (*callbackEvent)( - [this](ThreadPool::Callback callback) { - queueCallbackOnJSThread(callback, false); + [this](ThreadPool::Callback callback, ThreadPool::Callback cancelCallback) { + queueCallbackOnJSThread(callback, cancelCallback, false); }, [callbackCondition, callbackMutex, &hasCompleted]() { std::lock_guard lock(*callbackMutex); @@ -347,6 +347,11 @@ namespace nodegit { worker->WorkComplete(); worker->Destroy(); }, + [worker]() { + worker->Cancel(); + worker->WorkComplete(); + worker->Destroy(); + }, true ); } @@ -354,6 +359,7 @@ namespace nodegit { } } + // TODO add a cancel callback to `OnPostCallbackFn` which can be used on nodegit terminate void Orchestrator::OrchestratorImpl::PostCallbackEvent(ThreadPool::OnPostCallbackFn onPostCallback) { std::lock_guard lock(*executorEventsMutex); std::shared_ptr callbackEvent(new Executor::CallbackEvent(onPostCallback)); @@ -417,7 +423,7 @@ namespace nodegit { std::shared_ptr TakeNextJob(); - void QueueCallbackOnJSThread(ThreadPool::Callback callback, bool isWork); + void QueueCallbackOnJSThread(ThreadPool::Callback callback, ThreadPool::Callback cancelCallback, bool isWork); static void RunJSThreadCallbacksFromOrchestrator(uv_async_t *handle); @@ -425,24 +431,33 @@ namespace nodegit { static void RunLoopCallbacks(uv_async_t *handle); + void Shutdown(); + private: + bool isMarkedForDeletion; + struct JSThreadCallback { - JSThreadCallback(ThreadPool::Callback callback, bool isWork) - : isWork(isWork), callback(callback) + JSThreadCallback(ThreadPool::Callback callback, ThreadPool::Callback cancelCallback, bool isWork) + : isWork(isWork), callback(callback), cancelCallback(cancelCallback) {} JSThreadCallback() - : isWork(false), callback(nullptr) + : isWork(false), callback(nullptr), cancelCallback(nullptr) {} - void operator()() { + void performCallback() { callback(); } + void cancel() { + cancelCallback(); + } + bool isWork; private: ThreadPool::Callback callback; + ThreadPool::Callback cancelCallback; }; void RunLoopCallbacks(); @@ -461,7 +476,8 @@ namespace nodegit { }; ThreadPoolImpl::ThreadPoolImpl(int numberOfThreads, uv_loop_t *loop) - : orchestratorJobMutex(new std::mutex), + : isMarkedForDeletion(false), + orchestratorJobMutex(new std::mutex), jsThreadCallbackMutex(new std::mutex) { uv_async_init(loop, &jsThreadCallbackAsync, RunLoopCallbacks); @@ -472,7 +488,7 @@ namespace nodegit { for (int i = 0; i < numberOfThreads; i++) { orchestrators.emplace_back( - std::bind(&ThreadPoolImpl::QueueCallbackOnJSThread, this, _1, _2), + std::bind(&ThreadPoolImpl::QueueCallbackOnJSThread, this, _1, _2, _3), std::bind(&ThreadPoolImpl::TakeNextJob, this) ); } @@ -483,8 +499,7 @@ namespace nodegit { // there is work on the thread pool - reference the handle so // node doesn't terminate uv_ref((uv_handle_t *)&jsThreadCallbackAsync); - std::shared_ptr job(new Orchestrator::AsyncWorkJob(worker)); - orchestratorJobQueue.emplace(job); + orchestratorJobQueue.emplace(new Orchestrator::AsyncWorkJob(worker)); workInProgressCount++; orchestratorJobCondition.notify_one(); } @@ -504,11 +519,21 @@ namespace nodegit { return orchestratorJob; } - void ThreadPoolImpl::QueueCallbackOnJSThread(ThreadPool::Callback callback, bool isWork) { - // push the callback into the queue - std::lock_guard lock(*jsThreadCallbackMutex); + void ThreadPoolImpl::QueueCallbackOnJSThread(ThreadPool::Callback callback, ThreadPool::Callback cancelCallback, bool isWork) { + std::unique_lock lock(*jsThreadCallbackMutex); + // When the threadpool is shutting down, we want to free up the executors to also shutdown + // that means that we need to cancel all non-work callbacks as soon as we see them and + // we know that we are shutting down + if (isMarkedForDeletion && !isWork) { + // we don't know how long the cancelCallback will take, and it certainly doesn't need the lock + // while we're running it, so unlock it immediately. + lock.unlock(); + cancelCallback(); + return; + } + bool queueWasEmpty = jsThreadCallbackQueue.empty(); - jsThreadCallbackQueue.emplace(callback, isWork); + jsThreadCallbackQueue.emplace(callback, cancelCallback, isWork); // we only trigger RunLoopCallbacks via the jsThreadCallbackAsync handle if the queue // was empty. Otherwise, we depend on RunLoopCallbacks to re-trigger itself if (queueWasEmpty) { @@ -520,9 +545,7 @@ namespace nodegit { static_cast(handle->data)->RunLoopCallbacks(); } - // TODO every time this is called, if there is a cleanup operation in progress - // for the current context and there is workInProgress, we should ping the - // cleanup async handle which is not created yet + // NOTE this should theoretically never be triggered during a cleanup operation void ThreadPoolImpl::RunLoopCallbacks() { Nan::HandleScope scope; v8::Local context = Nan::GetCurrentContext(); @@ -531,13 +554,12 @@ namespace nodegit { std::unique_lock lock(*jsThreadCallbackMutex); // get the next callback to run JSThreadCallback jsThreadCallback = jsThreadCallbackQueue.front(); + jsThreadCallbackQueue.pop(); lock.unlock(); - jsThreadCallback(); + jsThreadCallback.performCallback(); lock.lock(); - // pop the queue, and if necessary, re-trigger RunLoopCallbacks - jsThreadCallbackQueue.pop(); if (!jsThreadCallbackQueue.empty()) { uv_async_send(&jsThreadCallbackAsync); } @@ -553,6 +575,80 @@ namespace nodegit { } } + void ThreadPoolImpl::Shutdown() { + std::queue> cancelledJobs; + std::queue cancelledCallbacks; + { + std::unique_lock orchestratorLock(*orchestratorJobMutex, std::defer_lock); + std::unique_lock jsThreadLock(*jsThreadCallbackMutex, std::defer_lock); + std::lock(orchestratorLock, jsThreadLock); + + // Once we've marked for deletion, we will start cancelling all callbacks + // when an attempt to queue a callback is made + isMarkedForDeletion = true; + // We want to grab all of the jobs that have been queued and run their cancel routines + // so that we can clean up their resources + orchestratorJobQueue.swap(cancelledJobs); + // We also want to grab all callbacks that have been queued so that we can + // run their cancel routines, this will help terminate the async workers + // that are currently being executed complete so that the threads + // running them can exit cleanly + jsThreadCallbackQueue.swap(cancelledCallbacks); + // Pushing a ShutdownJob into the queue will instruct all threads + // to start their shutdown process when they see the job is available. + orchestratorJobQueue.emplace(new Orchestrator::ShutdownJob); + + if (workInProgressCount) { + // unref the jsThreadCallback for all work in progress + // it will not be used after this function has completed + while (workInProgressCount--) { + uv_unref((uv_handle_t *)&jsThreadCallbackAsync); + } + } + + orchestratorJobCondition.notify_all(); + } + + Nan::HandleScope scope; + v8::Local context = Nan::GetCurrentContext(); + node::CallbackScope callbackScope(context->GetIsolate(), Nan::New(), {0, 0}); + + while (cancelledJobs.size()) { + std::shared_ptr cancelledJob = cancelledJobs.front(); + std::shared_ptr asyncWorkJob = std::static_pointer_cast(cancelledJob); + + asyncWorkJob->worker->Cancel(); + asyncWorkJob->worker->WorkComplete(); + asyncWorkJob->worker->Destroy(); + + cancelledJobs.pop(); + } + + // We need to cancel all callbacks that were scheduled before the shutdown + // request went through. This will help finish any work any currently operating + // executors are undertaking + while (cancelledCallbacks.size()) { + JSThreadCallback cancelledCallback = cancelledCallbacks.front(); + cancelledCallback.cancel(); + cancelledCallbacks.pop(); + } + + std::for_each(orchestrators.begin(), orchestrators.end(), [](Orchestrator &orchestrator) { + orchestrator.WaitForThreadClose(); + }); + + // After we have completed waiting for all threads to close + // we will need to cleanup the rest of the completion callbacks + // from workers that were still running when the shutdown signal + // was sent + std::lock_guard jsThreadLock(*jsThreadCallbackMutex); + while (jsThreadCallbackQueue.size()) { + JSThreadCallback jsThreadCallback = jsThreadCallbackQueue.front(); + jsThreadCallback.cancel(); + jsThreadCallbackQueue.pop(); + } + } + ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop) : impl(new ThreadPoolImpl(numberOfThreads, loop)) {} @@ -571,6 +667,10 @@ namespace nodegit { return Executor::GetCurrentAsyncResource(); } + void ThreadPool::Shutdown() { + impl->Shutdown(); + } + void ThreadPool::InitializeGlobal() { git_custom_tls_set_callbacks( Executor::RetrieveTLSForLibgit2ChildThread, diff --git a/generate/templates/partials/async_function.cc b/generate/templates/partials/async_function.cc index b0ad8ab4b..a48629385 100644 --- a/generate/templates/partials/async_function.cc +++ b/generate/templates/partials/async_function.cc @@ -27,6 +27,7 @@ NAN_METHOD({{ cppClassName }}::{{ cppFunctionName }}) { {%if arg.payload.globalPayload %} globalPayload->{{ arg.name }} = NULL; {%else%} + // NOTE this is a dead path baton->{{ arg.payload.name }} = NULL; {%endif%} } @@ -35,6 +36,7 @@ NAN_METHOD({{ cppClassName }}::{{ cppFunctionName }}) { {%if arg.payload.globalPayload %} globalPayload->{{ arg.name }} = new Nan::Callback(info[{{ arg.jsArg }}].As()); {%else%} + // NOTE this is a dead path baton->{{ arg.payload.name }} = new Nan::Callback(info[{{ arg.jsArg }}].As()); {%endif%} } diff --git a/generate/templates/partials/callback_helpers.cc b/generate/templates/partials/callback_helpers.cc index 8bbc2ecd0..8759d26b1 100644 --- a/generate/templates/partials/callback_helpers.cc +++ b/generate/templates/partials/callback_helpers.cc @@ -12,7 +12,11 @@ baton.{{ arg.name }} = {{ arg.name }}; {% endeach %} - return baton.ExecuteAsync({{ cppFunctionName }}_{{ cbFunction.name }}_async); + return baton.ExecuteAsync({{ cppFunctionName }}_{{ cbFunction.name }}_async, {{ cppFunctionName }}_{{ cbFunction.name }}_cancelAsync); +} + +void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_cancelAsync(void *untypedBaton) { + // TODO decide between defaultResult and cancellation on a per callback basis } void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(void *untypedBaton) { diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index 0b0dec1ee..0995bb384 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -141,10 +141,10 @@ if (instance->{{ field.name }}.WillBeThrottled()) { delete baton; } else if (instance->{{ field.name }}.ShouldWaitForResult()) { - baton->ExecuteAsync({{ field.name }}_async); + baton->ExecuteAsync({{ field.name }}_async, {{ field.name }}_cancelAsync); delete baton; } else { - baton->ExecuteAsync({{ field.name }}_async, nodegit::deleteBaton); + baton->ExecuteAsync({{ field.name }}_async, {{ field.name }}_cancelAsync, nodegit::deleteBaton); } return; {% else %} @@ -154,16 +154,19 @@ result = baton->defaultResult; delete baton; } else if (instance->{{ field.name }}.ShouldWaitForResult()) { - result = baton->ExecuteAsync({{ field.name }}_async); + result = baton->ExecuteAsync({{ field.name }}_async, {{ field.name }}_cancelAsync); delete baton; } else { result = baton->defaultResult; - baton->ExecuteAsync({{ field.name }}_async, nodegit::deleteBaton); + baton->ExecuteAsync({{ field.name }}_async, {{ field.name }}_cancelAsync, nodegit::deleteBaton); } return result; {% endif %} } + void {{ cppClassName }}::{{ field.name }}_cancelAsync(void *untypedBaton) { + + } void {{ cppClassName }}::{{ field.name }}_async(void *untypedBaton) { Nan::HandleScope scope; diff --git a/generate/templates/templates/class_header.h b/generate/templates/templates/class_header.h index ca394278f..94b60fce1 100644 --- a/generate/templates/templates/class_header.h +++ b/generate/templates/templates/class_header.h @@ -73,6 +73,7 @@ class {{ cppClassName }} : public {% endeach %} ); + static void {{ function.cppFunctionName }}_{{ arg.name }}_cancelAsync(void *baton); static void {{ function.cppFunctionName }}_{{ arg.name }}_async(void *baton); static void {{ function.cppFunctionName }}_{{ arg.name }}_promiseCompleted(bool isFulfilled, nodegit::AsyncBaton *_baton, v8::Local result); class {{ function.cppFunctionName }}_{{ arg.name|titleCase }}Baton : public nodegit::AsyncBatonWithResult<{{ arg.return.type }}> { diff --git a/generate/templates/templates/struct_header.h b/generate/templates/templates/struct_header.h index a80e7a5cf..6ef905c92 100644 --- a/generate/templates/templates/struct_header.h +++ b/generate/templates/templates/struct_header.h @@ -53,6 +53,7 @@ class {{ cppClassName }} : public NodeGitWrapper<{{ cppClassName }}Traits> { {% endeach %} ); + static void {{ field.name }}_cancelAsync(void *baton); static void {{ field.name }}_async(void *baton); static void {{ field.name }}_promiseCompleted(bool isFulfilled, nodegit::AsyncBaton *_baton, v8::Local result); {% if field.return.type == 'void' %} From 317f3a788ff71b92b70ec4a0ac9b3cc8372e0b4c Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Fri, 28 Aug 2020 12:30:42 -0700 Subject: [PATCH 10/40] Complete implementation of libgit2 callback cancellation --- generate/input/callbacks.json | 133 +++--- generate/input/descriptor.json | 3 + generate/templates/manual/src/thread_pool.cc | 28 +- .../templates/partials/callback_helpers.cc | 4 +- .../templates/partials/field_accessors.cc | 6 +- test/tests/filter.js | 379 +----------------- 6 files changed, 118 insertions(+), 435 deletions(-) diff --git a/generate/input/callbacks.json b/generate/input/callbacks.json index c8db807e5..e1cf6d828 100644 --- a/generate/input/callbacks.json +++ b/generate/input/callbacks.json @@ -14,7 +14,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_apply_hunk_cb": { @@ -32,7 +33,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_attr_foreach_cb": { @@ -54,7 +56,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_checkout_notify_cb": { @@ -88,7 +91,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": 0 } }, "git_checkout_progress_cb": { @@ -111,10 +115,7 @@ } ], "return": { - "type": "int", - "noResults": 1, - "success": 0, - "error": -1, + "type": "void", "throttle": 100 } }, @@ -130,10 +131,8 @@ } ], "return": { - "type": "int", - "noResults": 1, - "success": 0, - "error": -1 + "type": "void", + "throttle": 100 } }, "git_commit_signing_cb": { @@ -159,7 +158,8 @@ "type": "int", "noResults": -30, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_config_foreach_cb": { @@ -177,7 +177,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_credential_acquire_cb": { @@ -208,7 +209,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_diff_binary_cb": { @@ -230,7 +232,8 @@ "type": "int", "noResults": 0, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_diff_file_cb": { @@ -253,7 +256,7 @@ "noResults": 1, "success": 0, "error": -1, - "throttle": 100 + "cancel": -1 } }, "git_diff_hunk_cb": { @@ -275,7 +278,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_diff_line_cb": { @@ -301,7 +305,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_diff_notify_cb": { @@ -328,7 +333,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_diff_progress_cb": { @@ -355,7 +361,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_filter_apply_fn": { @@ -385,7 +392,8 @@ "type": "int", "noResults": -30, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_filter_check_fn": { @@ -411,7 +419,8 @@ "type": "int", "noResults": -30, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_filter_cleanup_fn": { @@ -440,7 +449,8 @@ "type": "int", "noResults": 0, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_filter_shutdown_fn": { @@ -473,7 +483,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_indexer_progress_cb": { @@ -492,6 +503,7 @@ "noResults": 0, "success": 0, "error": -1, + "cancel": -1, "throttle": 100 } }, @@ -514,7 +526,8 @@ "type": "int", "noResults": 0, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_odb_foreach_cb": { @@ -527,7 +540,14 @@ "name": "payload", "cType": "void *" } - ] + ], + "return": { + "type": "int", + "noResults": 0, + "success": 0, + "error": -1, + "cancel": -1 + } }, "git_packbuilder_foreach_cb": { "args": [ @@ -548,7 +568,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_push_update_reference_cb": { @@ -570,7 +591,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_remote_create_cb": { @@ -601,7 +623,8 @@ "type": "int", "noResults": 0, "success": 0, - "error": 1 + "error": -1, + "cancel": -1 } }, "git_repository_create_cb": { @@ -628,7 +651,8 @@ "type": "int", "noResults": 0, "success": 0, - "error": 1 + "error": 1, + "cancel": -1 } }, "git_reference_foreach_cb": { @@ -646,7 +670,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_reference_foreach_name_cb": { @@ -664,7 +689,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_repository_fetchhead_foreach_cb": { @@ -694,7 +720,8 @@ "type": "int", "noResults": 0, "success": 0, - "error": 1 + "error": -1, + "cancel": -1 } }, "git_repository_mergehead_foreach_cb": { @@ -712,7 +739,8 @@ "type": "int", "noResults": 0, "success": 0, - "error": 1 + "error": -1, + "cancel": -1 } }, "git_revwalk_hide_cb": { @@ -730,7 +758,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_stash_apply_progress_cb": { @@ -749,6 +778,7 @@ "noResults":0, "success": 0, "error": -1, + "cancel": -1, "throttle": 100 } }, @@ -775,7 +805,8 @@ "type": "int", "noResults":0, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_status_cb": { @@ -797,7 +828,8 @@ "type": "int", "noResults": 0, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_submodule_cb": { @@ -819,7 +851,8 @@ "type": "int", "noResults": 0, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_tag_foreach_cb": { @@ -841,7 +874,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_push_transfer_progress_cb": { @@ -868,6 +902,7 @@ "noResults": 0, "success": 0, "error": -1, + "cancel": -1, "throttle": 100 } }, @@ -891,7 +926,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_transport_certificate_check_cb": { @@ -917,7 +953,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_transport_message_cb": { @@ -939,7 +976,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_treebuilder_filter_cb": { @@ -957,7 +995,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": 0 } }, "git_treewalk_cb": { @@ -979,7 +1018,8 @@ "type": "int", "noResults": 1, "success": 0, - "error": -1 + "error": -1, + "cancel": -1 } }, "git_url_resolve_cb": { @@ -1005,7 +1045,8 @@ "type": "int", "noResults": -30, "success": 0, - "error": -1 + "error": -1, + "cancel": -30 } } } diff --git a/generate/input/descriptor.json b/generate/input/descriptor.json index e5f18b568..05cc25f1f 100644 --- a/generate/input/descriptor.json +++ b/generate/input/descriptor.json @@ -1487,6 +1487,9 @@ "git2/sys/filter.h" ], "fields": { + "cleanup": { + "ignore": true + }, "stream": { "ignore": true } diff --git a/generate/templates/manual/src/thread_pool.cc b/generate/templates/manual/src/thread_pool.cc index 4cf5a23f6..17e8d758a 100644 --- a/generate/templates/manual/src/thread_pool.cc +++ b/generate/templates/manual/src/thread_pool.cc @@ -470,7 +470,7 @@ namespace nodegit { // completion and async callbacks to be performed on the loop std::queue jsThreadCallbackQueue; std::unique_ptr jsThreadCallbackMutex; - uv_async_t jsThreadCallbackAsync; + uv_async_t *jsThreadCallbackAsync; std::vector orchestrators; }; @@ -478,11 +478,12 @@ namespace nodegit { ThreadPoolImpl::ThreadPoolImpl(int numberOfThreads, uv_loop_t *loop) : isMarkedForDeletion(false), orchestratorJobMutex(new std::mutex), - jsThreadCallbackMutex(new std::mutex) + jsThreadCallbackMutex(new std::mutex), + jsThreadCallbackAsync(new uv_async_t) { - uv_async_init(loop, &jsThreadCallbackAsync, RunLoopCallbacks); - jsThreadCallbackAsync.data = this; - uv_unref((uv_handle_t *)&jsThreadCallbackAsync); + uv_async_init(loop, jsThreadCallbackAsync, RunLoopCallbacks); + jsThreadCallbackAsync->data = this; + uv_unref((uv_handle_t *)jsThreadCallbackAsync); workInProgressCount = 0; @@ -498,7 +499,7 @@ namespace nodegit { std::lock_guard lock(*orchestratorJobMutex); // there is work on the thread pool - reference the handle so // node doesn't terminate - uv_ref((uv_handle_t *)&jsThreadCallbackAsync); + uv_ref((uv_handle_t *)jsThreadCallbackAsync); orchestratorJobQueue.emplace(new Orchestrator::AsyncWorkJob(worker)); workInProgressCount++; orchestratorJobCondition.notify_one(); @@ -537,7 +538,7 @@ namespace nodegit { // we only trigger RunLoopCallbacks via the jsThreadCallbackAsync handle if the queue // was empty. Otherwise, we depend on RunLoopCallbacks to re-trigger itself if (queueWasEmpty) { - uv_async_send(&jsThreadCallbackAsync); + uv_async_send(jsThreadCallbackAsync); } } @@ -561,7 +562,7 @@ namespace nodegit { lock.lock(); if (!jsThreadCallbackQueue.empty()) { - uv_async_send(&jsThreadCallbackAsync); + uv_async_send(jsThreadCallbackAsync); } // if there is no ongoing work / completion processing, node doesn't need @@ -570,7 +571,7 @@ namespace nodegit { std::lock_guard orchestratorLock(*orchestratorJobMutex); workInProgressCount--; if (!workInProgressCount) { - uv_unref((uv_handle_t *)&jsThreadCallbackAsync); + uv_unref((uv_handle_t *)jsThreadCallbackAsync); } } } @@ -602,7 +603,7 @@ namespace nodegit { // unref the jsThreadCallback for all work in progress // it will not be used after this function has completed while (workInProgressCount--) { - uv_unref((uv_handle_t *)&jsThreadCallbackAsync); + uv_unref((uv_handle_t *)jsThreadCallbackAsync); } } @@ -647,6 +648,13 @@ namespace nodegit { jsThreadCallback.cancel(); jsThreadCallbackQueue.pop(); } + + // NOTE We are deliberately leaking this pointer because `async` cleanup in + // node has not completely landed yet. Trying to cleanup this pointer + // is probably not worth the fight as it's very little memory lost per context + // When all LTS versions of node and Electron support async cleanup, we should + // be heading back to cleanup this + uv_close((uv_handle_t *)jsThreadCallbackAsync, nullptr); } ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop) diff --git a/generate/templates/partials/callback_helpers.cc b/generate/templates/partials/callback_helpers.cc index 8759d26b1..36e20891c 100644 --- a/generate/templates/partials/callback_helpers.cc +++ b/generate/templates/partials/callback_helpers.cc @@ -16,7 +16,9 @@ } void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_cancelAsync(void *untypedBaton) { - // TODO decide between defaultResult and cancellation on a per callback basis + {{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton* baton = static_cast<{{ cppFunctionName }}_{{ cbFunction.name|titleCase }}Baton*>(untypedBaton); + baton->result = {{ cbFunction.return.cancel }}; + baton->Done(); } void {{ cppClassName }}::{{ cppFunctionName }}_{{ cbFunction.name }}_async(void *untypedBaton) { diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index 0995bb384..5bbe0b2f6 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -165,7 +165,11 @@ } void {{ cppClassName }}::{{ field.name }}_cancelAsync(void *untypedBaton) { - + {{ field.name|titleCase }}Baton* baton = static_cast<{{ field.name|titleCase }}Baton*>(untypedBaton); + {% if field.return.type != "void" %} + baton->result = {{ field.return.cancel }}; + {% endif %} + baton->Done(); } void {{ cppClassName }}::{{ field.name }}_async(void *untypedBaton) { diff --git a/test/tests/filter.js b/test/tests/filter.js index b03587d77..995e2542f 100644 --- a/test/tests/filter.js +++ b/test/tests/filter.js @@ -167,233 +167,6 @@ describe("Filter", function() { }); }); - describe("Initialize", function(){ - it("initializes successfully", function() { - var test = this; - var initialized = false; - return Registry.register(filterName, { - initialize: function() { - initialized = true; - return NodeGit.Error.CODE.OK; - }, - apply: function() {}, - check: function() { - return NodeGit.Error.CODE.PASSTHROUGH; - } - }, 0) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - }) - .then(function() { - return fse.writeFile( - packageJsonPath, - "Changing content to trigger checkout" - ); - }) - .then(function() { - var opts = { - checkoutStrategy: Checkout.STRATEGY.FORCE, - paths: "package.json" - }; - return Checkout.head(test.repository, opts); - }) - .then(function() { - assert.strictEqual(initialized, true); - }); - }); - - it("initializes successfully even on garbage collect", function() { - var test = this; - var initialized = false; - return Registry.register(filterName, { - initialize: function() { - initialized = true; - return NodeGit.Error.CODE.OK; - }, - apply: function() {}, - check: function() { - return NodeGit.Error.CODE.PASSTHROUGH; - } - }, 0) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - garbageCollect(); - - return fse.writeFile( - packageJsonPath, - "Changing content to trigger checkout" - ); - }) - .then(function() { - var opts = { - checkoutStrategy: Checkout.STRATEGY.FORCE, - paths: "package.json" - }; - return Checkout.head(test.repository, opts); - }) - .then(function() { - assert.strictEqual(initialized, true); - }); - }); - - it("does not initialize successfully", function() { - var test = this; - var initialized = false; - return Registry.register(filterName, { - initialize: function() { - initialized = true; - return NodeGit.Error.CODE.ERROR; - }, - apply: function() {}, - check: function() { - return NodeGit.Error.CODE.PASSTHROUGH; - } - }, 0) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - }) - .then(function() { - return fse.writeFile( - packageJsonPath, - "Changing content to trigger checkout" - ); - }) - .then(function() { - var opts = { - checkoutStrategy: Checkout.STRATEGY.FORCE, - paths: "package.json" - }; - return Checkout.head(test.repository, opts); - }) - .then(function(head) { - assert.strictEqual( - head, - undefined, - "Should not have actually checked out" - ); - }) - .catch(function(error) { - assert.strictEqual(initialized, true); - }); - }); - }); - - describe("Shutdown", function() { - it("filter successfully shuts down", function() { - var test = this; - var shutdown = false; - return Registry.register(filterName, { - apply: function() {}, - check: function(){ - return NodeGit.Error.CODE.PASSTHROUGH; - }, - shutdown: function(){ - shutdown = true; - } - }, 0) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - return fse.writeFile( - packageJsonPath, - "Changing content to trigger checkout", - { encoding: "utf-8" } - ); - }) - .then(function() { - var opts = { - checkoutStrategy: Checkout.STRATEGY.FORCE, - paths: "package.json" - }; - return Checkout.head(test.repository, opts); - }) - .then(function() { - return Registry.unregister(filterName); - }) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - assert.strictEqual(shutdown, true); - }); - }); - - it("filter successfully shuts down on garbage collect", function() { - var test = this; - var shutdown = false; - return Registry.register(filterName, { - apply: function() {}, - check: function(){ - return NodeGit.Error.CODE.PASSTHROUGH; - }, - shutdown: function(){ - shutdown = true; - } - }, 0) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - return fse.writeFile( - packageJsonPath, - "Changing content to trigger checkout", - { encoding: "utf-8" } - ); - }) - .then(function() { - var opts = { - checkoutStrategy: Checkout.STRATEGY.FORCE, - paths: "package.json" - }; - return Checkout.head(test.repository, opts); - }) - .then(function() { - garbageCollect(); - - return Registry.unregister(filterName); - }) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - assert.strictEqual(shutdown, true); - }); - }); - - it("shutdown completes even if there is an error", function() { - var test = this; - var shutdown = false; - return Registry.register(filterName, { - apply: function() {}, - check: function(){ - return NodeGit.Error.CODE.PASSTHROUGH; - }, - shutdown: function(){ - shutdown = true; - throw new Error("I failed"); - } - }, 0) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - return fse.writeFile( - packageJsonPath, - "Changing content to trigger checkout", - { encoding: "utf-8" } - ); - }) - .then(function() { - var opts = { - checkoutStrategy: Checkout.STRATEGY.FORCE, - paths: "package.json" - }; - return Checkout.head(test.repository, opts); - }) - .then(function() { - return Registry.unregister(filterName); - }) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - assert.strictEqual(shutdown, true); - }) - .catch(function(error) { - assert.fail(error, null, "The operation should not have failed"); - }); - }); - }); - describe("Apply", function() { before(function() { var test = this; @@ -672,8 +445,7 @@ describe("Filter", function() { check: function(src, attr) { return src.path() === "README.md" ? 0 : NodeGit.Error.CODE.PASSTHROUGH; - }, - cleanup: function() {} + } }, 0) .then(function(result) { assert.strictEqual(result, NodeGit.Error.CODE.OK); @@ -727,8 +499,7 @@ describe("Filter", function() { check: function(src, attr) { return src.path() === "README.md" ? 0 : NodeGit.Error.CODE.PASSTHROUGH; - }, - cleanup: function() {} + } }, 0) .then(function(result) { garbageCollect(); @@ -775,152 +546,6 @@ describe("Filter", function() { }); }); - describe("Cleanup", function() { - it("is called successfully", function() { - var test = this; - var cleaned = false; - return Registry.register(filterName, { - initialize: function() { - return NodeGit.Error.CODE.OK; - }, - apply: function() { - return NodeGit.Error.CODE.OK; - }, - check: function() { - return NodeGit.Error.CODE.OK; - }, - cleanup: function() { - cleaned = true; - } - }, 0) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - }) - .then(function() { - var packageContent = fse.readFileSync( - packageJsonPath, - "utf-8" - ); - assert.notEqual(packageContent, ""); - - return fse.writeFile( - packageJsonPath, - "Changing content to trigger checkout", - { encoding: "utf-8" } - ); - }) - .then(function() { - var opts = { - checkoutStrategy: Checkout.STRATEGY.FORCE, - paths: "package.json" - }; - return Checkout.head(test.repository, opts); - }) - .then(function() { - assert.strictEqual(cleaned, true); - }); - }); - - it("is called successfully with gc", function() { - var test = this; - var cleaned = false; - return Registry.register(filterName, { - initialize: function() { - return NodeGit.Error.CODE.OK; - }, - apply: function() { - return NodeGit.Error.CODE.OK; - }, - check: function() { - return NodeGit.Error.CODE.OK; - }, - cleanup: function() { - cleaned = true; - } - }, 0) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - }) - .then(function() { - var packageContent = fse.readFileSync( - packageJsonPath, - "utf-8" - ); - assert.notEqual(packageContent, ""); - - garbageCollect(); - return fse.writeFile( - packageJsonPath, - "Changing content to trigger checkout", - { encoding: "utf-8" } - ); - }) - .then(function() { - var opts = { - checkoutStrategy: Checkout.STRATEGY.FORCE, - paths: "package.json" - }; - return Checkout.head(test.repository, opts); - }) - .then(function() { - assert.strictEqual(cleaned, true); - }); - }); - - it("is not called when check returns GIT_PASSTHROUGH", function() { - var test = this; - var cleaned = false; - - return Registry.register(filterName, { - initialize: function() { - return NodeGit.Error.CODE.OK; - }, - apply: function() { - return NodeGit.Error.CODE.OK; - }, - check: function() { - return NodeGit.Error.CODE.PASSTHROUGH; - }, - cleanup: function() { - cleaned = true; - } - }, 0) - .then(function(result) { - assert.strictEqual(result, NodeGit.Error.CODE.OK); - }) - .then(function() { - var packageContent = fse.readFileSync( - packageJsonPath, - "utf-8" - ); - var readmeContent = fse.readFileSync( - readmePath, - "utf-8" - ); - - assert.notEqual(packageContent, ""); - assert.notEqual(readmeContent, "Initialized"); - }) - .then(function() { - return fse.writeFile( - packageJsonPath, - "Changing content to trigger checkout", - { encoding: "utf-8" } - ); - }) - .then(function() { - var opts = { - checkoutStrategy: Checkout.STRATEGY.FORCE, - paths: "README.md" - }; - return Checkout.head(test.repository, opts); - }) - .then(function() { - assert.notStrictEqual(cleaned, true); - }); - }); - }); - describe("Manually Apply", function() { beforeEach(function() { var test = this; From fa0376937945ff7aab58917f0e5bac300e0263b3 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Thu, 27 Aug 2020 12:29:51 -0700 Subject: [PATCH 11/40] Add HandleErrorCallback skeleton --- generate/templates/partials/async_function.cc | 18 ++++++++++++++++++ generate/templates/templates/class_header.h | 1 + 2 files changed, 19 insertions(+) diff --git a/generate/templates/partials/async_function.cc b/generate/templates/partials/async_function.cc index a48629385..02f097119 100644 --- a/generate/templates/partials/async_function.cc +++ b/generate/templates/partials/async_function.cc @@ -131,6 +131,24 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::Execute() { {%endif%} } +void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback() { + puts("HandleErrorCallback"); + // inspect the baton for any pointers that have been initialized + // free any pointers that have been initialized + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + baton->error = NULL; + } + + // free the baton + delete baton; + baton = NULL; +} + void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleOKCallback() { {%if return.isResultOrError %} if (baton->error_code >= GIT_OK) { diff --git a/generate/templates/templates/class_header.h b/generate/templates/templates/class_header.h index 94b60fce1..e828b7f89 100644 --- a/generate/templates/templates/class_header.h +++ b/generate/templates/templates/class_header.h @@ -142,6 +142,7 @@ class {{ cppClassName }} : public , baton(_baton) {}; ~{{ function.cppFunctionName }}Worker() {}; void Execute(); + void HandleErrorCallback(); void HandleOKCallback(); nodegit::LockMaster AcquireLocks(); From f259b6fb36e5ff6e9c5a6ebae445207dbf1920bd Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Thu, 27 Aug 2020 12:30:34 -0700 Subject: [PATCH 12/40] Add skeleton of manual templates --- generate/templates/manual/clone/clone.cc | 2 ++ generate/templates/manual/commit/extract_signature.cc | 2 ++ generate/templates/manual/filter_list/load.cc | 2 ++ generate/templates/manual/filter_source/repo.cc | 2 ++ generate/templates/manual/patches/convenient_patches.cc | 2 ++ generate/templates/manual/remote/ls.cc | 2 ++ generate/templates/manual/repository/get_references.cc | 2 ++ generate/templates/manual/repository/get_remotes.cc | 2 ++ generate/templates/manual/repository/get_submodules.cc | 2 ++ generate/templates/manual/repository/refresh_references.cc | 2 ++ generate/templates/manual/revwalk/commit_walk.cc | 2 ++ generate/templates/manual/revwalk/fast_walk.cc | 2 ++ generate/templates/manual/revwalk/file_history_walk.cc | 2 ++ 13 files changed, 26 insertions(+) diff --git a/generate/templates/manual/clone/clone.cc b/generate/templates/manual/clone/clone.cc index 9541f05f3..0050affce 100644 --- a/generate/templates/manual/clone/clone.cc +++ b/generate/templates/manual/clone/clone.cc @@ -125,6 +125,8 @@ void GitClone::CloneWorker::Execute() { } } +void GitClone::CloneWorker::HandleErrorCallback() {} + void GitClone::CloneWorker::HandleOKCallback() { if (baton->error_code == GIT_OK) { v8::Local to; diff --git a/generate/templates/manual/commit/extract_signature.cc b/generate/templates/manual/commit/extract_signature.cc index 5c66cbe3f..dfe45347a 100644 --- a/generate/templates/manual/commit/extract_signature.cc +++ b/generate/templates/manual/commit/extract_signature.cc @@ -94,6 +94,8 @@ void GitCommit::ExtractSignatureWorker::Execute() } } +void GitCommit::ExtractSignatureWorker::HandleErrorCallback() {} + void GitCommit::ExtractSignatureWorker::HandleOKCallback() { if (baton->error_code == GIT_OK) diff --git a/generate/templates/manual/filter_list/load.cc b/generate/templates/manual/filter_list/load.cc index 0107dfb81..49d13c72b 100644 --- a/generate/templates/manual/filter_list/load.cc +++ b/generate/templates/manual/filter_list/load.cc @@ -131,6 +131,8 @@ void GitFilterList::LoadWorker::Execute() { } } +void GitFilterList::LoadWorker::HandleErrorCallback() {} + void GitFilterList::LoadWorker::HandleOKCallback() { if (baton->error_code == GIT_OK) { v8::Local to; diff --git a/generate/templates/manual/filter_source/repo.cc b/generate/templates/manual/filter_source/repo.cc index 96d2d5e0b..f11257aba 100644 --- a/generate/templates/manual/filter_source/repo.cc +++ b/generate/templates/manual/filter_source/repo.cc @@ -46,6 +46,8 @@ void GitFilterSource::RepoWorker::Execute() { } } +void GitFilterSource::RepoWorker::HandleErrorCallback() {} + void GitFilterSource::RepoWorker::HandleOKCallback() { if (baton->error_code == GIT_OK) { v8::Local to; diff --git a/generate/templates/manual/patches/convenient_patches.cc b/generate/templates/manual/patches/convenient_patches.cc index 5630de1dc..bd4559ea0 100644 --- a/generate/templates/manual/patches/convenient_patches.cc +++ b/generate/templates/manual/patches/convenient_patches.cc @@ -77,6 +77,8 @@ void GitPatch::ConvenientFromDiffWorker::Execute() { } } +void GitPatch::ConvenientFromDiffWorker::HandleErrorCallback() {} + void GitPatch::ConvenientFromDiffWorker::HandleOKCallback() { if (baton->out != NULL) { unsigned int size = baton->out->size(); diff --git a/generate/templates/manual/remote/ls.cc b/generate/templates/manual/remote/ls.cc index f3adfee9f..233af8a27 100644 --- a/generate/templates/manual/remote/ls.cc +++ b/generate/templates/manual/remote/ls.cc @@ -51,6 +51,8 @@ void GitRemote::ReferenceListWorker::Execute() } } +void GitRemote::ReferenceListWorker::HandleErrorCallback() {} + void GitRemote::ReferenceListWorker::HandleOKCallback() { if (baton->out != NULL) diff --git a/generate/templates/manual/repository/get_references.cc b/generate/templates/manual/repository/get_references.cc index 313f0479b..343ecb712 100644 --- a/generate/templates/manual/repository/get_references.cc +++ b/generate/templates/manual/repository/get_references.cc @@ -81,6 +81,8 @@ void GitRepository::GetReferencesWorker::Execute() } } +void GitRepository::GetReferencesWorker::HandleErrorCallback() {} + void GitRepository::GetReferencesWorker::HandleOKCallback() { if (baton->out != NULL) diff --git a/generate/templates/manual/repository/get_remotes.cc b/generate/templates/manual/repository/get_remotes.cc index eb562de9f..2def104bb 100644 --- a/generate/templates/manual/repository/get_remotes.cc +++ b/generate/templates/manual/repository/get_remotes.cc @@ -83,6 +83,8 @@ void GitRepository::GetRemotesWorker::Execute() } } +void GitRepository::GetRemotesWorker::HandleErrorCallback() {} + void GitRepository::GetRemotesWorker::HandleOKCallback() { if (baton->out != NULL) diff --git a/generate/templates/manual/repository/get_submodules.cc b/generate/templates/manual/repository/get_submodules.cc index 425754ad9..0a486dbc0 100644 --- a/generate/templates/manual/repository/get_submodules.cc +++ b/generate/templates/manual/repository/get_submodules.cc @@ -62,6 +62,8 @@ void GitRepository::GetSubmodulesWorker::Execute() } } +void GitRepository::GetSubmodulesWorker::HandleErrorCallback() {} + void GitRepository::GetSubmodulesWorker::HandleOKCallback() { if (baton->out != NULL) diff --git a/generate/templates/manual/repository/refresh_references.cc b/generate/templates/manual/repository/refresh_references.cc index 80a783708..763280322 100644 --- a/generate/templates/manual/repository/refresh_references.cc +++ b/generate/templates/manual/repository/refresh_references.cc @@ -589,6 +589,8 @@ void GitRepository::RefreshReferencesWorker::Execute() } } +void GitRepository::RefreshReferencesWorker::HandleErrorCallback() {} + void GitRepository::RefreshReferencesWorker::HandleOKCallback() { if (baton->out != NULL) diff --git a/generate/templates/manual/revwalk/commit_walk.cc b/generate/templates/manual/revwalk/commit_walk.cc index b2486e899..dd14d0d75 100644 --- a/generate/templates/manual/revwalk/commit_walk.cc +++ b/generate/templates/manual/revwalk/commit_walk.cc @@ -207,6 +207,8 @@ void GitRevwalk::CommitWalkWorker::Execute() { } } +void GitRevwalk::CommitWalkWorker::HandleErrorCallback() {} + void GitRevwalk::CommitWalkWorker::HandleOKCallback() { if (baton->out != NULL) { std::vector *out = static_cast *>(baton->out); diff --git a/generate/templates/manual/revwalk/fast_walk.cc b/generate/templates/manual/revwalk/fast_walk.cc index 72d092eba..db4564cd3 100644 --- a/generate/templates/manual/revwalk/fast_walk.cc +++ b/generate/templates/manual/revwalk/fast_walk.cc @@ -72,6 +72,8 @@ void GitRevwalk::FastWalkWorker::Execute() } } +void GitRevwalk::FastWalkWorker::HandleErrorCallback() {} + void GitRevwalk::FastWalkWorker::HandleOKCallback() { if (baton->out != NULL) diff --git a/generate/templates/manual/revwalk/file_history_walk.cc b/generate/templates/manual/revwalk/file_history_walk.cc index 6ba2e0b33..1efe6c33f 100644 --- a/generate/templates/manual/revwalk/file_history_walk.cc +++ b/generate/templates/manual/revwalk/file_history_walk.cc @@ -424,6 +424,8 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() baton->file_path = NULL; } +void GitRevwalk::FileHistoryWalkWorker::HandleErrorCallback() {} + void GitRevwalk::FileHistoryWalkWorker::HandleOKCallback() { if (baton->out != NULL) { From 50444d0892afaa23e866332cfa7201c806dfd2c6 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Thu, 27 Aug 2020 14:58:39 -0700 Subject: [PATCH 13/40] Fix Repository.discover description --- lib/repository.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/repository.js b/lib/repository.js index 49e968e4f..32987d44f 100644 --- a/lib/repository.js +++ b/lib/repository.js @@ -319,7 +319,7 @@ function performRebase( } /** - * Creates a branch with the passed in name pointing to the commit + * Look for a git repository, returning its path. * * @async * @param {String} startPath The base path where the lookup starts. From c0ff5e68efc9aa2afa9c4acb3c439c394741acdb Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Thu, 27 Aug 2020 15:06:03 -0700 Subject: [PATCH 14/40] Cleanup additional baton members --- generate/templates/partials/async_function.cc | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/generate/templates/partials/async_function.cc b/generate/templates/partials/async_function.cc index 02f097119..34ee27ac5 100644 --- a/generate/templates/partials/async_function.cc +++ b/generate/templates/partials/async_function.cc @@ -132,7 +132,7 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::Execute() { } void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback() { - puts("HandleErrorCallback"); + puts("{{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback()"); // inspect the baton for any pointers that have been initialized // free any pointers that have been initialized if (baton->error) { @@ -144,6 +144,54 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback() { baton->error = NULL; } + {%each args|argsInfo as arg %} + {%if arg.shouldAlloc %} + {%if not arg.isCppClassStringOrArray %} + {%elsif arg | isOid %} + if (baton->{{ arg.name}}NeedsFree) { + baton->{{ arg.name}}NeedsFree = false; + free((void*)baton->{{ arg.name }}); + } + {%elsif arg.isCallbackFunction %} + {%if not arg.payload.globalPayload %} + delete baton->{{ arg.payload.name }}; + {%endif%} + {%elsif arg.globalPayload %} + delete ({{ cppFunctionName}}_globalPayload*)baton->{{ arg.name }}; + {%else%} + free((void*)baton->{{ arg.name }}); + {%endif%} + {%endif%} + {%endeach%} + + {%each args|argsInfo as arg %} + {%if arg.isCppClassStringOrArray %} + {%if arg.freeFunctionName %} + {{ arg.freeFunctionName }}(baton->{{ arg.name }}); + {%elsif not arg.isConst%} + free((void *)baton->{{ arg.name }}); + {%endif%} + {%elsif arg | isOid %} + if (baton->{{ arg.name}}NeedsFree) { + baton->{{ arg.name}}NeedsFree = false; + free((void *)baton->{{ arg.name }}); + } + {%elsif arg.isCallbackFunction %} + {%if not arg.payload.globalPayload %} + delete baton->{{ arg.payload.name }}; + {%endif%} + {%elsif arg.globalPayload %} + delete ({{ cppFunctionName}}_globalPayload*)baton->{{ arg.name }}; + {%endif%} + {%if arg.cppClassName == "GitBuf" %} + {%if cppFunctionName == "Set" %} + {%else%} + git_buf_dispose(baton->{{ arg.name }}); + free((void *)baton->{{ arg.name }}); + {%endif%} + {%endif%} + {%endeach%} + // free the baton delete baton; baton = NULL; From ce43d922f222ebbb2e92c39187774728140ee557 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Thu, 27 Aug 2020 15:41:42 -0700 Subject: [PATCH 15/40] Call callback from HandleErrorCallback --- generate/templates/partials/async_function.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/generate/templates/partials/async_function.cc b/generate/templates/partials/async_function.cc index 34ee27ac5..522de672b 100644 --- a/generate/templates/partials/async_function.cc +++ b/generate/templates/partials/async_function.cc @@ -133,6 +133,14 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::Execute() { void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback() { puts("{{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback()"); + + v8::Local err = Nan::To(Nan::Error(ErrorMessage())).ToLocalChecked(); + Nan::Set(err, Nan::New("errorFunction").ToLocalChecked(), Nan::New("{{ jsClassName }}.{{ jsFunctionName }}").ToLocalChecked()); + v8::Local argv[1] = { + err + }; + callback->Call(1, argv, async_resource); + // inspect the baton for any pointers that have been initialized // free any pointers that have been initialized if (baton->error) { From 223bf7e7ceac8514fdc41d0d9425a2ffcd1aef72 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Fri, 28 Aug 2020 14:08:40 -0700 Subject: [PATCH 16/40] Don't call callback if AsyncWorker is cancelled We aren't allowed to call back to JavaScript in this case --- generate/templates/manual/include/async_worker.h | 5 +++++ generate/templates/manual/src/async_worker.cc | 6 ++++++ generate/templates/partials/async_function.cc | 14 ++++++++------ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/generate/templates/manual/include/async_worker.h b/generate/templates/manual/include/async_worker.h index 77159a605..13005040a 100644 --- a/generate/templates/manual/include/async_worker.h +++ b/generate/templates/manual/include/async_worker.h @@ -22,6 +22,11 @@ namespace nodegit { // This is used to inform libgit2 callbacks what asyncResource // they should use when working with any javascript Nan::AsyncResource *GetAsyncResource(); + + bool GetIsCancelled() const; + + private: + bool isCancelled = false; }; } diff --git a/generate/templates/manual/src/async_worker.cc b/generate/templates/manual/src/async_worker.cc index bc7162a84..26a07b098 100644 --- a/generate/templates/manual/src/async_worker.cc +++ b/generate/templates/manual/src/async_worker.cc @@ -6,6 +6,8 @@ namespace nodegit { {} void AsyncWorker::Cancel() { + isCancelled = true; + // We use Nan::AsyncWorker's ErrorMessage flow // to trigger `HandleErrorCallback` for cancellation // of AsyncWork @@ -15,4 +17,8 @@ namespace nodegit { Nan::AsyncResource *AsyncWorker::GetAsyncResource() { return async_resource; } + + bool AsyncWorker::GetIsCancelled() const { + return isCancelled; + } } diff --git a/generate/templates/partials/async_function.cc b/generate/templates/partials/async_function.cc index 522de672b..e428c46f1 100644 --- a/generate/templates/partials/async_function.cc +++ b/generate/templates/partials/async_function.cc @@ -134,12 +134,14 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::Execute() { void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback() { puts("{{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback()"); - v8::Local err = Nan::To(Nan::Error(ErrorMessage())).ToLocalChecked(); - Nan::Set(err, Nan::New("errorFunction").ToLocalChecked(), Nan::New("{{ jsClassName }}.{{ jsFunctionName }}").ToLocalChecked()); - v8::Local argv[1] = { - err - }; - callback->Call(1, argv, async_resource); + if (!GetIsCancelled()) { + v8::Local err = Nan::To(Nan::Error(ErrorMessage())).ToLocalChecked(); + Nan::Set(err, Nan::New("errorFunction").ToLocalChecked(), Nan::New("{{ jsClassName }}.{{ jsFunctionName }}").ToLocalChecked()); + v8::Local argv[1] = { + err + }; + callback->Call(1, argv, async_resource); + } // inspect the baton for any pointers that have been initialized // free any pointers that have been initialized From af5585cb984d380b2a42cfea1d98de58465e12ef Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Fri, 28 Aug 2020 14:11:57 -0700 Subject: [PATCH 17/40] Cleanup HandleErrorCallback template --- generate/templates/partials/async_function.cc | 7 ------- 1 file changed, 7 deletions(-) diff --git a/generate/templates/partials/async_function.cc b/generate/templates/partials/async_function.cc index e428c46f1..982b07c46 100644 --- a/generate/templates/partials/async_function.cc +++ b/generate/templates/partials/async_function.cc @@ -132,8 +132,6 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::Execute() { } void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback() { - puts("{{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback()"); - if (!GetIsCancelled()) { v8::Local err = Nan::To(Nan::Error(ErrorMessage())).ToLocalChecked(); Nan::Set(err, Nan::New("errorFunction").ToLocalChecked(), Nan::New("{{ jsClassName }}.{{ jsFunctionName }}").ToLocalChecked()); @@ -143,15 +141,12 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback() { callback->Call(1, argv, async_resource); } - // inspect the baton for any pointers that have been initialized - // free any pointers that have been initialized if (baton->error) { if (baton->error->message) { free((void *)baton->error->message); } free((void *)baton->error); - baton->error = NULL; } {%each args|argsInfo as arg %} @@ -202,9 +197,7 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback() { {%endif%} {%endeach%} - // free the baton delete baton; - baton = NULL; } void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleOKCallback() { From 610104b4c24704f45d3b5de03d072e0aed7d33a7 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Fri, 28 Aug 2020 16:52:06 -0700 Subject: [PATCH 18/40] Fill in skeleton HandleErrorCallbacks --- generate/templates/manual/clone/clone.cc | 12 +++++++++++- .../templates/manual/commit/extract_signature.cc | 12 +++++++++++- generate/templates/manual/filter_list/load.cc | 12 +++++++++++- generate/templates/manual/filter_source/repo.cc | 12 +++++++++++- .../templates/manual/patches/convenient_patches.cc | 12 +++++++++++- generate/templates/manual/remote/ls.cc | 14 +++++++++++++- .../templates/manual/repository/get_references.cc | 14 +++++++++++++- .../templates/manual/repository/get_remotes.cc | 14 +++++++++++++- .../templates/manual/repository/get_submodules.cc | 14 +++++++++++++- .../manual/repository/refresh_references.cc | 14 +++++++++++++- generate/templates/manual/revwalk/commit_walk.cc | 14 +++++++++++++- generate/templates/manual/revwalk/fast_walk.cc | 14 +++++++++++++- .../templates/manual/revwalk/file_history_walk.cc | 14 +++++++++++++- 13 files changed, 159 insertions(+), 13 deletions(-) diff --git a/generate/templates/manual/clone/clone.cc b/generate/templates/manual/clone/clone.cc index 0050affce..75a48cbfe 100644 --- a/generate/templates/manual/clone/clone.cc +++ b/generate/templates/manual/clone/clone.cc @@ -125,7 +125,17 @@ void GitClone::CloneWorker::Execute() { } } -void GitClone::CloneWorker::HandleErrorCallback() {} +void GitClone::CloneWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitClone::CloneWorker::HandleOKCallback() { if (baton->error_code == GIT_OK) { diff --git a/generate/templates/manual/commit/extract_signature.cc b/generate/templates/manual/commit/extract_signature.cc index dfe45347a..e9f39d5ac 100644 --- a/generate/templates/manual/commit/extract_signature.cc +++ b/generate/templates/manual/commit/extract_signature.cc @@ -94,7 +94,17 @@ void GitCommit::ExtractSignatureWorker::Execute() } } -void GitCommit::ExtractSignatureWorker::HandleErrorCallback() {} +void GitCommit::ExtractSignatureWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitCommit::ExtractSignatureWorker::HandleOKCallback() { diff --git a/generate/templates/manual/filter_list/load.cc b/generate/templates/manual/filter_list/load.cc index 49d13c72b..ba6eb50c2 100644 --- a/generate/templates/manual/filter_list/load.cc +++ b/generate/templates/manual/filter_list/load.cc @@ -131,7 +131,17 @@ void GitFilterList::LoadWorker::Execute() { } } -void GitFilterList::LoadWorker::HandleErrorCallback() {} +void GitFilterList::LoadWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitFilterList::LoadWorker::HandleOKCallback() { if (baton->error_code == GIT_OK) { diff --git a/generate/templates/manual/filter_source/repo.cc b/generate/templates/manual/filter_source/repo.cc index f11257aba..0f4317f25 100644 --- a/generate/templates/manual/filter_source/repo.cc +++ b/generate/templates/manual/filter_source/repo.cc @@ -46,7 +46,17 @@ void GitFilterSource::RepoWorker::Execute() { } } -void GitFilterSource::RepoWorker::HandleErrorCallback() {} +void GitFilterSource::RepoWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitFilterSource::RepoWorker::HandleOKCallback() { if (baton->error_code == GIT_OK) { diff --git a/generate/templates/manual/patches/convenient_patches.cc b/generate/templates/manual/patches/convenient_patches.cc index bd4559ea0..15e2df5d8 100644 --- a/generate/templates/manual/patches/convenient_patches.cc +++ b/generate/templates/manual/patches/convenient_patches.cc @@ -77,7 +77,17 @@ void GitPatch::ConvenientFromDiffWorker::Execute() { } } -void GitPatch::ConvenientFromDiffWorker::HandleErrorCallback() {} +void GitPatch::ConvenientFromDiffWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitPatch::ConvenientFromDiffWorker::HandleOKCallback() { if (baton->out != NULL) { diff --git a/generate/templates/manual/remote/ls.cc b/generate/templates/manual/remote/ls.cc index 233af8a27..5eb45c7f9 100644 --- a/generate/templates/manual/remote/ls.cc +++ b/generate/templates/manual/remote/ls.cc @@ -51,7 +51,17 @@ void GitRemote::ReferenceListWorker::Execute() } } -void GitRemote::ReferenceListWorker::HandleErrorCallback() {} +void GitRemote::ReferenceListWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitRemote::ReferenceListWorker::HandleOKCallback() { @@ -98,4 +108,6 @@ void GitRemote::ReferenceListWorker::HandleOKCallback() { callback->Call(0, NULL, async_resource); } + + delete baton; } diff --git a/generate/templates/manual/repository/get_references.cc b/generate/templates/manual/repository/get_references.cc index 343ecb712..8f5f70263 100644 --- a/generate/templates/manual/repository/get_references.cc +++ b/generate/templates/manual/repository/get_references.cc @@ -81,7 +81,17 @@ void GitRepository::GetReferencesWorker::Execute() } } -void GitRepository::GetReferencesWorker::HandleErrorCallback() {} +void GitRepository::GetReferencesWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitRepository::GetReferencesWorker::HandleOKCallback() { @@ -137,4 +147,6 @@ void GitRepository::GetReferencesWorker::HandleOKCallback() { callback->Call(0, NULL, async_resource); } + + delete baton; } diff --git a/generate/templates/manual/repository/get_remotes.cc b/generate/templates/manual/repository/get_remotes.cc index 2def104bb..7f7503fc9 100644 --- a/generate/templates/manual/repository/get_remotes.cc +++ b/generate/templates/manual/repository/get_remotes.cc @@ -83,7 +83,17 @@ void GitRepository::GetRemotesWorker::Execute() } } -void GitRepository::GetRemotesWorker::HandleErrorCallback() {} +void GitRepository::GetRemotesWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitRepository::GetRemotesWorker::HandleOKCallback() { @@ -139,4 +149,6 @@ void GitRepository::GetRemotesWorker::HandleOKCallback() { callback->Call(0, NULL, async_resource); } + + delete baton; } diff --git a/generate/templates/manual/repository/get_submodules.cc b/generate/templates/manual/repository/get_submodules.cc index 0a486dbc0..28a89046b 100644 --- a/generate/templates/manual/repository/get_submodules.cc +++ b/generate/templates/manual/repository/get_submodules.cc @@ -62,7 +62,17 @@ void GitRepository::GetSubmodulesWorker::Execute() } } -void GitRepository::GetSubmodulesWorker::HandleErrorCallback() {} +void GitRepository::GetSubmodulesWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitRepository::GetSubmodulesWorker::HandleOKCallback() { @@ -118,4 +128,6 @@ void GitRepository::GetSubmodulesWorker::HandleOKCallback() { callback->Call(0, NULL, async_resource); } + + delete baton; } diff --git a/generate/templates/manual/repository/refresh_references.cc b/generate/templates/manual/repository/refresh_references.cc index 763280322..ef220b8d7 100644 --- a/generate/templates/manual/repository/refresh_references.cc +++ b/generate/templates/manual/repository/refresh_references.cc @@ -589,7 +589,17 @@ void GitRepository::RefreshReferencesWorker::Execute() } } -void GitRepository::RefreshReferencesWorker::HandleErrorCallback() {} +void GitRepository::RefreshReferencesWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitRepository::RefreshReferencesWorker::HandleOKCallback() { @@ -678,4 +688,6 @@ void GitRepository::RefreshReferencesWorker::HandleOKCallback() { callback->Call(0, NULL, async_resource); } + + delete baton; } diff --git a/generate/templates/manual/revwalk/commit_walk.cc b/generate/templates/manual/revwalk/commit_walk.cc index dd14d0d75..09e3e4831 100644 --- a/generate/templates/manual/revwalk/commit_walk.cc +++ b/generate/templates/manual/revwalk/commit_walk.cc @@ -207,7 +207,17 @@ void GitRevwalk::CommitWalkWorker::Execute() { } } -void GitRevwalk::CommitWalkWorker::HandleErrorCallback() {} +void GitRevwalk::CommitWalkWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitRevwalk::CommitWalkWorker::HandleOKCallback() { if (baton->out != NULL) { @@ -252,4 +262,6 @@ void GitRevwalk::CommitWalkWorker::HandleOKCallback() { } else { callback->Call(0, NULL, async_resource); } + + delete baton; } diff --git a/generate/templates/manual/revwalk/fast_walk.cc b/generate/templates/manual/revwalk/fast_walk.cc index db4564cd3..76e151238 100644 --- a/generate/templates/manual/revwalk/fast_walk.cc +++ b/generate/templates/manual/revwalk/fast_walk.cc @@ -72,7 +72,17 @@ void GitRevwalk::FastWalkWorker::Execute() } } -void GitRevwalk::FastWalkWorker::HandleErrorCallback() {} +void GitRevwalk::FastWalkWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitRevwalk::FastWalkWorker::HandleOKCallback() { @@ -180,4 +190,6 @@ void GitRevwalk::FastWalkWorker::HandleOKCallback() callback->Call(0, NULL, async_resource); } } + + delete baton; } diff --git a/generate/templates/manual/revwalk/file_history_walk.cc b/generate/templates/manual/revwalk/file_history_walk.cc index 1efe6c33f..f0359abd3 100644 --- a/generate/templates/manual/revwalk/file_history_walk.cc +++ b/generate/templates/manual/revwalk/file_history_walk.cc @@ -424,7 +424,17 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() baton->file_path = NULL; } -void GitRevwalk::FileHistoryWalkWorker::HandleErrorCallback() {} +void GitRevwalk::FileHistoryWalkWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} void GitRevwalk::FileHistoryWalkWorker::HandleOKCallback() { @@ -483,4 +493,6 @@ void GitRevwalk::FileHistoryWalkWorker::HandleOKCallback() } callback->Call(0, NULL, async_resource); + + delete baton; } From 9a519001c2b48d5c60bf4bab194c9e44a4c750c9 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Fri, 28 Aug 2020 16:52:44 -0700 Subject: [PATCH 19/40] Add missing HandleErrorCallbacks --- .../manual/include/convenient_hunk.h | 1 + .../manual/include/convenient_patch.h | 1 + .../manual/include/filter_registry.h | 2 ++ .../templates/manual/src/convenient_hunk.cc | 4 +++ .../templates/manual/src/convenient_patch.cc | 4 +++ .../templates/manual/src/filter_registry.cc | 28 +++++++++++++++++-- 6 files changed, 38 insertions(+), 2 deletions(-) diff --git a/generate/templates/manual/include/convenient_hunk.h b/generate/templates/manual/include/convenient_hunk.h index 3953a4680..dbdfb5c79 100644 --- a/generate/templates/manual/include/convenient_hunk.h +++ b/generate/templates/manual/include/convenient_hunk.h @@ -65,6 +65,7 @@ class ConvenientHunk : public Nan::ObjectWrap { , baton(_baton) {}; ~LinesWorker() {}; void Execute(); + void HandleErrorCallback(); void HandleOKCallback(); nodegit::LockMaster AcquireLocks(); diff --git a/generate/templates/manual/include/convenient_patch.h b/generate/templates/manual/include/convenient_patch.h index ea8dcf95d..148c14ff2 100644 --- a/generate/templates/manual/include/convenient_patch.h +++ b/generate/templates/manual/include/convenient_patch.h @@ -77,6 +77,7 @@ class ConvenientPatch : public Nan::ObjectWrap { , baton(_baton) {}; ~HunksWorker() {}; void Execute(); + void HandleErrorCallback(); void HandleOKCallback(); nodegit::LockMaster AcquireLocks(); diff --git a/generate/templates/manual/include/filter_registry.h b/generate/templates/manual/include/filter_registry.h index cefcbcaed..c329b33e7 100644 --- a/generate/templates/manual/include/filter_registry.h +++ b/generate/templates/manual/include/filter_registry.h @@ -54,6 +54,7 @@ class GitFilterRegistry : public Nan::ObjectWrap { : nodegit::AsyncWorker(callback, "nodegit:AsyncWorker:FilterRegistry:Register"), baton(_baton) {}; ~RegisterWorker() {}; void Execute(); + void HandleErrorCallback(); void HandleOKCallback(); nodegit::LockMaster AcquireLocks(); @@ -67,6 +68,7 @@ class GitFilterRegistry : public Nan::ObjectWrap { : nodegit::AsyncWorker(callback, "nodegit:AsyncWorker:FilterRegistry:Unregister"), baton(_baton) {}; ~UnregisterWorker() {}; void Execute(); + void HandleErrorCallback(); void HandleOKCallback(); nodegit::LockMaster AcquireLocks(); diff --git a/generate/templates/manual/src/convenient_hunk.cc b/generate/templates/manual/src/convenient_hunk.cc index ff1c0967b..1d53d3327 100644 --- a/generate/templates/manual/src/convenient_hunk.cc +++ b/generate/templates/manual/src/convenient_hunk.cc @@ -130,6 +130,8 @@ void ConvenientHunk::LinesWorker::Execute() { } } +void ConvenientHunk::LinesWorker::HandleErrorCallback() {} + void ConvenientHunk::LinesWorker::HandleOKCallback() { unsigned int size = baton->lines->size(); Local result = Nan::New(size); @@ -145,6 +147,8 @@ void ConvenientHunk::LinesWorker::HandleOKCallback() { result }; callback->Call(2, argv, async_resource); + + delete baton; } NAN_METHOD(ConvenientHunk::OldStart) { diff --git a/generate/templates/manual/src/convenient_patch.cc b/generate/templates/manual/src/convenient_patch.cc index 01a437fb9..e9489a48e 100644 --- a/generate/templates/manual/src/convenient_patch.cc +++ b/generate/templates/manual/src/convenient_patch.cc @@ -263,6 +263,8 @@ void ConvenientPatch::HunksWorker::Execute() { } } +void ConvenientPatch::HunksWorker::HandleErrorCallback() {} + void ConvenientPatch::HunksWorker::HandleOKCallback() { unsigned int size = baton->hunks->size(); Local result = Nan::New(size); @@ -278,6 +280,8 @@ void ConvenientPatch::HunksWorker::HandleOKCallback() { result }; callback->Call(2, argv, async_resource); + + delete baton; } NAN_METHOD(ConvenientPatch::LineStats) { diff --git a/generate/templates/manual/src/filter_registry.cc b/generate/templates/manual/src/filter_registry.cc index 688b9efbf..a026b102d 100644 --- a/generate/templates/manual/src/filter_registry.cc +++ b/generate/templates/manual/src/filter_registry.cc @@ -96,6 +96,18 @@ void GitFilterRegistry::RegisterWorker::Execute() { } } +void GitFilterRegistry::RegisterWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} + void GitFilterRegistry::RegisterWorker::HandleOKCallback() { if (baton->error_code == GIT_OK) { v8::Local result = Nan::New(baton->error_code); @@ -134,8 +146,8 @@ void GitFilterRegistry::RegisterWorker::HandleOKCallback() { else { callback->Call(0, NULL, async_resource); } + delete baton; - return; } NAN_METHOD(GitFilterRegistry::GitFilterUnregister) { @@ -187,6 +199,18 @@ void GitFilterRegistry::UnregisterWorker::Execute() { } } +void GitFilterRegistry::UnregisterWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + delete baton; +} + void GitFilterRegistry::UnregisterWorker::HandleOKCallback() { if (baton->error_code == GIT_OK) { nodegit::Context *nodegitContext = nodegit::Context::GetCurrentContext(); @@ -228,6 +252,6 @@ void GitFilterRegistry::UnregisterWorker::HandleOKCallback() { else { callback->Call(0, NULL, async_resource); } + delete baton; - return; } From bed58a4cfc712b7ec339d52ceb9d9e7dbfe1c63d Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Mon, 31 Aug 2020 17:54:07 -0700 Subject: [PATCH 20/40] Value initialize batons Ensure baton data members are value initialized (since batons are trivial types) --- generate/templates/partials/async_function.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate/templates/partials/async_function.cc b/generate/templates/partials/async_function.cc index 982b07c46..846b19912 100644 --- a/generate/templates/partials/async_function.cc +++ b/generate/templates/partials/async_function.cc @@ -6,7 +6,7 @@ NAN_METHOD({{ cppClassName }}::{{ cppFunctionName }}) { return Nan::ThrowError("Callback is required and must be a Function."); } - {{ cppFunctionName }}Baton* baton = new {{ cppFunctionName }}Baton; + {{ cppFunctionName }}Baton* baton = new {{ cppFunctionName }}Baton(); baton->error_code = GIT_OK; baton->error = NULL; From 74fe6e18fe814bc356d73ea0e0f2ea63c44bd179 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Mon, 31 Aug 2020 18:00:01 -0700 Subject: [PATCH 21/40] Use freeFunctionName to free returned args on error --- generate/scripts/helpers.js | 2 ++ generate/templates/partials/async_function.cc | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/generate/scripts/helpers.js b/generate/scripts/helpers.js index 9b9afb015..2af653f63 100644 --- a/generate/scripts/helpers.js +++ b/generate/scripts/helpers.js @@ -170,6 +170,8 @@ var Helpers = { } } + type.freeFunctionName = libgitType.freeFunctionName; + // we don't want to overwrite the c type of the passed in type _.merge(type, descriptor.types[normalizedType.replace("git_", "")] || {}, { cType: type.cType }); } diff --git a/generate/templates/partials/async_function.cc b/generate/templates/partials/async_function.cc index 846b19912..6b64ad443 100644 --- a/generate/templates/partials/async_function.cc +++ b/generate/templates/partials/async_function.cc @@ -166,13 +166,14 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleErrorCallback() { {%else%} free((void*)baton->{{ arg.name }}); {%endif%} + {%elsif arg.freeFunctionName|and arg.isReturn|and arg.selfFreeing %} + {{ arg.freeFunctionName }}(baton->{{ arg.name }}); {%endif%} {%endeach%} {%each args|argsInfo as arg %} {%if arg.isCppClassStringOrArray %} {%if arg.freeFunctionName %} - {{ arg.freeFunctionName }}(baton->{{ arg.name }}); {%elsif not arg.isConst%} free((void *)baton->{{ arg.name }}); {%endif%} @@ -331,6 +332,8 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleOKCallback() { {%else%} free((void*)baton->{{ arg.name }}); {%endif%} + {%elsif arg.freeFunctionName|and arg.isReturn|and arg.selfFreeing %} + {{ arg.freeFunctionName }}(baton->{{ arg.name }}); {%endif%} {%endeach%} } @@ -338,7 +341,6 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleOKCallback() { {%each args|argsInfo as arg %} {%if arg.isCppClassStringOrArray %} {%if arg.freeFunctionName %} - {{ arg.freeFunctionName }}(baton->{{ arg.name }}); {%elsif not arg.isConst%} free((void *)baton->{{ arg.name }}); {%endif%} From 046a7ad4d318061a7f2f84fd977b3a5b1d8a8344 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Wed, 2 Sep 2020 10:53:34 -0700 Subject: [PATCH 22/40] Value initialize manual template batons as well --- generate/templates/manual/clone/clone.cc | 2 +- generate/templates/manual/commit/extract_signature.cc | 2 +- generate/templates/manual/filter_list/load.cc | 2 +- generate/templates/manual/filter_source/repo.cc | 2 +- generate/templates/manual/patches/convenient_patches.cc | 2 +- generate/templates/manual/remote/ls.cc | 2 +- generate/templates/manual/repository/get_references.cc | 2 +- generate/templates/manual/repository/get_remotes.cc | 2 +- generate/templates/manual/repository/get_submodules.cc | 2 +- .../templates/manual/repository/refresh_references.cc | 4 ++-- generate/templates/manual/revwalk/commit_walk.cc | 2 +- generate/templates/manual/revwalk/fast_walk.cc | 2 +- generate/templates/manual/revwalk/file_history_walk.cc | 2 +- generate/templates/manual/src/convenient_hunk.cc | 2 +- generate/templates/manual/src/convenient_patch.cc | 8 ++++---- generate/templates/manual/src/filter_registry.cc | 4 ++-- 16 files changed, 21 insertions(+), 21 deletions(-) diff --git a/generate/templates/manual/clone/clone.cc b/generate/templates/manual/clone/clone.cc index 75a48cbfe..2dd6fa3e8 100644 --- a/generate/templates/manual/clone/clone.cc +++ b/generate/templates/manual/clone/clone.cc @@ -25,7 +25,7 @@ NAN_METHOD(GitClone::Clone) { return Nan::ThrowError("Callback is required and must be a Function."); } - CloneBaton *baton = new CloneBaton; + CloneBaton *baton = new CloneBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/commit/extract_signature.cc b/generate/templates/manual/commit/extract_signature.cc index e9f39d5ac..53f7d3e89 100644 --- a/generate/templates/manual/commit/extract_signature.cc +++ b/generate/templates/manual/commit/extract_signature.cc @@ -22,7 +22,7 @@ NAN_METHOD(GitCommit::ExtractSignature) } } - ExtractSignatureBaton* baton = new ExtractSignatureBaton; + ExtractSignatureBaton* baton = new ExtractSignatureBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/filter_list/load.cc b/generate/templates/manual/filter_list/load.cc index ba6eb50c2..b1899d7e9 100644 --- a/generate/templates/manual/filter_list/load.cc +++ b/generate/templates/manual/filter_list/load.cc @@ -39,7 +39,7 @@ NAN_METHOD(GitFilterList::Load) { return Nan::ThrowError("Callback is required and must be a Function."); } - LoadBaton *baton = new LoadBaton; + LoadBaton *baton = new LoadBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/filter_source/repo.cc b/generate/templates/manual/filter_source/repo.cc index 0f4317f25..5255f6fbf 100644 --- a/generate/templates/manual/filter_source/repo.cc +++ b/generate/templates/manual/filter_source/repo.cc @@ -12,7 +12,7 @@ NAN_METHOD(GitFilterSource::Repo) { return Nan::ThrowError("Callback is required and must be a Function."); } - RepoBaton *baton = new RepoBaton; + RepoBaton *baton = new RepoBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/patches/convenient_patches.cc b/generate/templates/manual/patches/convenient_patches.cc index 15e2df5d8..bbfa32e46 100644 --- a/generate/templates/manual/patches/convenient_patches.cc +++ b/generate/templates/manual/patches/convenient_patches.cc @@ -7,7 +7,7 @@ NAN_METHOD(GitPatch::ConvenientFromDiff) { return Nan::ThrowError("Callback is required and must be a Function."); } - ConvenientFromDiffBaton *baton = new ConvenientFromDiffBaton; + ConvenientFromDiffBaton *baton = new ConvenientFromDiffBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/remote/ls.cc b/generate/templates/manual/remote/ls.cc index 5eb45c7f9..31850de0e 100644 --- a/generate/templates/manual/remote/ls.cc +++ b/generate/templates/manual/remote/ls.cc @@ -4,7 +4,7 @@ NAN_METHOD(GitRemote::ReferenceList) return Nan::ThrowError("Callback is required and must be a Function."); } - ReferenceListBaton* baton = new ReferenceListBaton; + ReferenceListBaton* baton = new ReferenceListBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/repository/get_references.cc b/generate/templates/manual/repository/get_references.cc index 8f5f70263..f1ec6d3df 100644 --- a/generate/templates/manual/repository/get_references.cc +++ b/generate/templates/manual/repository/get_references.cc @@ -4,7 +4,7 @@ NAN_METHOD(GitRepository::GetReferences) return Nan::ThrowError("Callback is required and must be a Function."); } - GetReferencesBaton* baton = new GetReferencesBaton; + GetReferencesBaton* baton = new GetReferencesBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/repository/get_remotes.cc b/generate/templates/manual/repository/get_remotes.cc index 7f7503fc9..2e1d4ed29 100644 --- a/generate/templates/manual/repository/get_remotes.cc +++ b/generate/templates/manual/repository/get_remotes.cc @@ -4,7 +4,7 @@ NAN_METHOD(GitRepository::GetRemotes) return Nan::ThrowError("Callback is required and must be a Function."); } - GetRemotesBaton* baton = new GetRemotesBaton; + GetRemotesBaton* baton = new GetRemotesBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/repository/get_submodules.cc b/generate/templates/manual/repository/get_submodules.cc index 28a89046b..fc93240fd 100644 --- a/generate/templates/manual/repository/get_submodules.cc +++ b/generate/templates/manual/repository/get_submodules.cc @@ -4,7 +4,7 @@ NAN_METHOD(GitRepository::GetSubmodules) return Nan::ThrowError("Callback is required and must be a Function."); } - GetSubmodulesBaton* baton = new GetSubmodulesBaton; + GetSubmodulesBaton* baton = new GetSubmodulesBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/repository/refresh_references.cc b/generate/templates/manual/repository/refresh_references.cc index ef220b8d7..7c27dfec3 100644 --- a/generate/templates/manual/repository/refresh_references.cc +++ b/generate/templates/manual/repository/refresh_references.cc @@ -395,11 +395,11 @@ NAN_METHOD(GitRepository::RefreshReferences) return Nan::ThrowError("Callback is required and must be a Function."); } - RefreshReferencesBaton* baton = new RefreshReferencesBaton; + RefreshReferencesBaton* baton = new RefreshReferencesBaton(); baton->error_code = GIT_OK; baton->error = NULL; - baton->out = (void *)new RefreshReferencesData; + baton->out = (void *)new RefreshReferencesData(); baton->repo = Nan::ObjectWrap::Unwrap(info.This())->GetValue(); Nan::Callback *callback = new Nan::Callback(Local::Cast(info[0])); diff --git a/generate/templates/manual/revwalk/commit_walk.cc b/generate/templates/manual/revwalk/commit_walk.cc index 09e3e4831..00270703e 100644 --- a/generate/templates/manual/revwalk/commit_walk.cc +++ b/generate/templates/manual/revwalk/commit_walk.cc @@ -121,7 +121,7 @@ NAN_METHOD(GitRevwalk::CommitWalk) { } } - CommitWalkBaton* baton = new CommitWalkBaton; + CommitWalkBaton* baton = new CommitWalkBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/revwalk/fast_walk.cc b/generate/templates/manual/revwalk/fast_walk.cc index 76e151238..c9ae994cf 100644 --- a/generate/templates/manual/revwalk/fast_walk.cc +++ b/generate/templates/manual/revwalk/fast_walk.cc @@ -8,7 +8,7 @@ NAN_METHOD(GitRevwalk::FastWalk) return Nan::ThrowError("Callback is required and must be a Function."); } - FastWalkBaton* baton = new FastWalkBaton; + FastWalkBaton* baton = new FastWalkBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/revwalk/file_history_walk.cc b/generate/templates/manual/revwalk/file_history_walk.cc index f0359abd3..9a54f526d 100644 --- a/generate/templates/manual/revwalk/file_history_walk.cc +++ b/generate/templates/manual/revwalk/file_history_walk.cc @@ -192,7 +192,7 @@ NAN_METHOD(GitRevwalk::FileHistoryWalk) return Nan::ThrowError("Callback is required and must be a Function."); } - FileHistoryWalkBaton* baton = new FileHistoryWalkBaton; + FileHistoryWalkBaton* baton = new FileHistoryWalkBaton(); baton->error_code = GIT_OK; baton->error = NULL; diff --git a/generate/templates/manual/src/convenient_hunk.cc b/generate/templates/manual/src/convenient_hunk.cc index 1d53d3327..2fa1de17b 100644 --- a/generate/templates/manual/src/convenient_hunk.cc +++ b/generate/templates/manual/src/convenient_hunk.cc @@ -96,7 +96,7 @@ NAN_METHOD(ConvenientHunk::Lines) { return Nan::ThrowError("Callback is required and must be a Function."); } - LinesBaton *baton = new LinesBaton; + LinesBaton *baton = new LinesBaton(); baton->hunk = Nan::ObjectWrap::Unwrap(info.This())->GetValue(); diff --git a/generate/templates/manual/src/convenient_patch.cc b/generate/templates/manual/src/convenient_patch.cc index e9489a48e..47044e9dd 100644 --- a/generate/templates/manual/src/convenient_patch.cc +++ b/generate/templates/manual/src/convenient_patch.cc @@ -33,7 +33,7 @@ void PatchDataFree(PatchData *patch) { } PatchData *createFromRaw(git_patch *raw) { - PatchData *patch = new PatchData; + PatchData *patch = new PatchData(); const git_diff_delta *delta = git_patch_get_delta(raw); patch->status = delta->status; @@ -56,7 +56,7 @@ PatchData *createFromRaw(git_patch *raw) { patch->hunks->reserve(patch->numHunks); for (unsigned int i = 0; i < patch->numHunks; ++i) { - HunkData *hunkData = new HunkData; + HunkData *hunkData = new HunkData(); const git_diff_hunk *hunk = NULL; int result = git_patch_get_hunk(&hunk, &hunkData->numLines, raw, i); if (result != 0) { @@ -212,7 +212,7 @@ NAN_METHOD(ConvenientPatch::Hunks) { return Nan::ThrowError("Callback is required and must be a Function."); } - HunksBaton *baton = new HunksBaton; + HunksBaton *baton = new HunksBaton(); baton->patch = Nan::ObjectWrap::Unwrap(info.This())->GetValue(); @@ -236,7 +236,7 @@ void ConvenientPatch::HunksWorker::Execute() { baton->hunks->reserve(baton->patch->numHunks); for (unsigned int i = 0; i < baton->patch->numHunks; ++i) { - HunkData *hunkData = new HunkData; + HunkData *hunkData = new HunkData(); hunkData->numLines = baton->patch->hunks->at(i)->numLines; hunkData->hunk.old_start = baton->patch->hunks->at(i)->hunk.old_start; hunkData->hunk.old_lines = baton->patch->hunks->at(i)->hunk.old_lines; diff --git a/generate/templates/manual/src/filter_registry.cc b/generate/templates/manual/src/filter_registry.cc index a026b102d..418c1142e 100644 --- a/generate/templates/manual/src/filter_registry.cc +++ b/generate/templates/manual/src/filter_registry.cc @@ -51,7 +51,7 @@ NAN_METHOD(GitFilterRegistry::GitFilterRegister) { return Nan::ThrowError("Callback is required and must be a Function."); } - FilterRegisterBaton *baton = new FilterRegisterBaton; + FilterRegisterBaton *baton = new FilterRegisterBaton(); baton->filter = Nan::ObjectWrap::Unwrap(Nan::To(info[1]).ToLocalChecked())->GetValue(); Nan::Utf8String name(Nan::To(info[0]).ToLocalChecked()); @@ -161,7 +161,7 @@ NAN_METHOD(GitFilterRegistry::GitFilterUnregister) { return Nan::ThrowError("Callback is required and must be a Function."); } - FilterUnregisterBaton *baton = new FilterUnregisterBaton; + FilterUnregisterBaton *baton = new FilterUnregisterBaton(); Nan::Utf8String name(Nan::To(info[0]).ToLocalChecked()); baton->filter_name = (char *)malloc(name.length() + 1); From bf7b13f7376cdac2e1aec68d15950696f900eb96 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Wed, 2 Sep 2020 13:43:44 -0700 Subject: [PATCH 23/40] Cleanup manual template batons --- generate/templates/manual/clone/clone.cc | 8 ++++++++ .../manual/commit/extract_signature.cc | 5 +++++ generate/templates/manual/filter_list/load.cc | 6 ++++++ .../templates/manual/filter_source/repo.cc | 2 ++ .../manual/patches/convenient_patches.cc | 7 +++++++ generate/templates/manual/remote/ls.cc | 2 ++ .../manual/repository/get_references.cc | 8 ++++++++ .../manual/repository/get_remotes.cc | 8 ++++++++ .../manual/repository/get_submodules.cc | 7 +++++++ .../manual/repository/refresh_references.cc | 5 ++++- .../templates/manual/revwalk/commit_walk.cc | 8 ++++++++ .../templates/manual/revwalk/fast_walk.cc | 7 +++++++ .../manual/revwalk/file_history_walk.cc | 8 ++++++++ .../templates/manual/src/convenient_hunk.cc | 13 ++++++++++--- .../templates/manual/src/convenient_patch.cc | 19 +++++++++++++++---- .../templates/manual/src/filter_registry.cc | 8 ++++++++ 16 files changed, 113 insertions(+), 8 deletions(-) diff --git a/generate/templates/manual/clone/clone.cc b/generate/templates/manual/clone/clone.cc index 2dd6fa3e8..a75ecb134 100644 --- a/generate/templates/manual/clone/clone.cc +++ b/generate/templates/manual/clone/clone.cc @@ -134,6 +134,11 @@ void GitClone::CloneWorker::HandleErrorCallback() { free((void *)baton->error); } + git_repository_free(baton->out); + + free((void*)baton->url); + free((void*)baton->local_path); + delete baton; } @@ -225,5 +230,8 @@ void GitClone::CloneWorker::HandleOKCallback() { } } + free((void*)baton->url); + free((void*)baton->local_path); + delete baton; } diff --git a/generate/templates/manual/commit/extract_signature.cc b/generate/templates/manual/commit/extract_signature.cc index 53f7d3e89..acad88e54 100644 --- a/generate/templates/manual/commit/extract_signature.cc +++ b/generate/templates/manual/commit/extract_signature.cc @@ -103,6 +103,11 @@ void GitCommit::ExtractSignatureWorker::HandleErrorCallback() { free((void *)baton->error); } + git_buf_dispose(&baton->signature); + git_buf_dispose(&baton->signed_data); + + free(baton->field); + delete baton; } diff --git a/generate/templates/manual/filter_list/load.cc b/generate/templates/manual/filter_list/load.cc index b1899d7e9..253d61e06 100644 --- a/generate/templates/manual/filter_list/load.cc +++ b/generate/templates/manual/filter_list/load.cc @@ -140,6 +140,10 @@ void GitFilterList::LoadWorker::HandleErrorCallback() { free((void *)baton->error); } + git_filter_list_free(baton->filters); + + free((void *)baton->path); + delete baton; } @@ -260,5 +264,7 @@ void GitFilterList::LoadWorker::HandleOKCallback() { } } + free((void *)baton->path); + delete baton; } diff --git a/generate/templates/manual/filter_source/repo.cc b/generate/templates/manual/filter_source/repo.cc index 5255f6fbf..2ca9f5f47 100644 --- a/generate/templates/manual/filter_source/repo.cc +++ b/generate/templates/manual/filter_source/repo.cc @@ -55,6 +55,8 @@ void GitFilterSource::RepoWorker::HandleErrorCallback() { free((void *)baton->error); } + git_repository_free(baton->out); + delete baton; } diff --git a/generate/templates/manual/patches/convenient_patches.cc b/generate/templates/manual/patches/convenient_patches.cc index bbfa32e46..af7a875ea 100644 --- a/generate/templates/manual/patches/convenient_patches.cc +++ b/generate/templates/manual/patches/convenient_patches.cc @@ -86,6 +86,13 @@ void GitPatch::ConvenientFromDiffWorker::HandleErrorCallback() { free((void *)baton->error); } + while (!baton->out->empty()) { + PatchDataFree(baton->out->back()); + baton->out->pop_back(); + } + + delete baton->out; + delete baton; } diff --git a/generate/templates/manual/remote/ls.cc b/generate/templates/manual/remote/ls.cc index 31850de0e..75a7ffed4 100644 --- a/generate/templates/manual/remote/ls.cc +++ b/generate/templates/manual/remote/ls.cc @@ -60,6 +60,8 @@ void GitRemote::ReferenceListWorker::HandleErrorCallback() { free((void *)baton->error); } + delete baton->out; + delete baton; } diff --git a/generate/templates/manual/repository/get_references.cc b/generate/templates/manual/repository/get_references.cc index f1ec6d3df..b868bc422 100644 --- a/generate/templates/manual/repository/get_references.cc +++ b/generate/templates/manual/repository/get_references.cc @@ -90,6 +90,14 @@ void GitRepository::GetReferencesWorker::HandleErrorCallback() { free((void *)baton->error); } + while (baton->out->size()) { + git_reference *referenceToFree = baton->out->back(); + baton->out->pop_back(); + git_reference_free(referenceToFree); + } + + delete baton->out; + delete baton; } diff --git a/generate/templates/manual/repository/get_remotes.cc b/generate/templates/manual/repository/get_remotes.cc index 2e1d4ed29..6e3a398ab 100644 --- a/generate/templates/manual/repository/get_remotes.cc +++ b/generate/templates/manual/repository/get_remotes.cc @@ -92,6 +92,14 @@ void GitRepository::GetRemotesWorker::HandleErrorCallback() { free((void *)baton->error); } + while (baton->out->size()) { + git_remote *remoteToFree = baton->out->back(); + baton->out->pop_back(); + git_remote_free(remoteToFree); + } + + delete baton->out; + delete baton; } diff --git a/generate/templates/manual/repository/get_submodules.cc b/generate/templates/manual/repository/get_submodules.cc index fc93240fd..5d3e07998 100644 --- a/generate/templates/manual/repository/get_submodules.cc +++ b/generate/templates/manual/repository/get_submodules.cc @@ -71,6 +71,13 @@ void GitRepository::GetSubmodulesWorker::HandleErrorCallback() { free((void *)baton->error); } + while (baton->out->size()) { + git_submodule_free(baton->out->back()); + baton->out->pop_back(); + } + + delete baton->out; + delete baton; } diff --git a/generate/templates/manual/repository/refresh_references.cc b/generate/templates/manual/repository/refresh_references.cc index 7c27dfec3..6257b26f7 100644 --- a/generate/templates/manual/repository/refresh_references.cc +++ b/generate/templates/manual/repository/refresh_references.cc @@ -598,6 +598,9 @@ void GitRepository::RefreshReferencesWorker::HandleErrorCallback() { free((void *)baton->error); } + RefreshReferencesData *refreshData = (RefreshReferencesData *)baton->out; + delete refreshData; + delete baton; } @@ -606,7 +609,7 @@ void GitRepository::RefreshReferencesWorker::HandleOKCallback() if (baton->out != NULL) { RefreshedRefModel::ensureSignatureRegexes(); - RefreshReferencesData *refreshData = (RefreshReferencesData *)baton->out; + auto refreshData = (RefreshReferencesData *)baton->out; v8::Local result = Nan::New(); Nan::Set( diff --git a/generate/templates/manual/revwalk/commit_walk.cc b/generate/templates/manual/revwalk/commit_walk.cc index 00270703e..e99543d52 100644 --- a/generate/templates/manual/revwalk/commit_walk.cc +++ b/generate/templates/manual/revwalk/commit_walk.cc @@ -216,6 +216,14 @@ void GitRevwalk::CommitWalkWorker::HandleErrorCallback() { free((void *)baton->error); } + auto out = static_cast *>(baton->out); + while (out->size()) { + delete out->back(); + out->pop_back(); + } + + delete out; + delete baton; } diff --git a/generate/templates/manual/revwalk/fast_walk.cc b/generate/templates/manual/revwalk/fast_walk.cc index c9ae994cf..989ed15c0 100644 --- a/generate/templates/manual/revwalk/fast_walk.cc +++ b/generate/templates/manual/revwalk/fast_walk.cc @@ -81,6 +81,13 @@ void GitRevwalk::FastWalkWorker::HandleErrorCallback() { free((void *)baton->error); } + while(!baton->out->empty()) { + free(baton->out->back()); + baton->out->pop_back(); + } + + delete baton->out; + delete baton; } diff --git a/generate/templates/manual/revwalk/file_history_walk.cc b/generate/templates/manual/revwalk/file_history_walk.cc index 9a54f526d..850e5d309 100644 --- a/generate/templates/manual/revwalk/file_history_walk.cc +++ b/generate/templates/manual/revwalk/file_history_walk.cc @@ -433,6 +433,14 @@ void GitRevwalk::FileHistoryWalkWorker::HandleErrorCallback() { free((void *)baton->error); } + for (unsigned int i = 0; i < baton->out->size(); ++i) { + delete static_cast(baton->out->at(i)); + } + + delete baton->out; + + free((void *)baton->file_path); + delete baton; } diff --git a/generate/templates/manual/src/convenient_hunk.cc b/generate/templates/manual/src/convenient_hunk.cc index 2fa1de17b..755783e74 100644 --- a/generate/templates/manual/src/convenient_hunk.cc +++ b/generate/templates/manual/src/convenient_hunk.cc @@ -99,6 +99,8 @@ NAN_METHOD(ConvenientHunk::Lines) { LinesBaton *baton = new LinesBaton(); baton->hunk = Nan::ObjectWrap::Unwrap(info.This())->GetValue(); + baton->lines = new std::vector; + baton->lines->reserve(baton->hunk->numLines); Nan::Callback *callback = new Nan::Callback(Local::Cast(info[0])); LinesWorker *worker = new LinesWorker(baton, callback); @@ -115,8 +117,6 @@ nodegit::LockMaster ConvenientHunk::LinesWorker::AcquireLocks() { } void ConvenientHunk::LinesWorker::Execute() { - baton->lines = new std::vector; - baton->lines->reserve(baton->hunk->numLines); for (unsigned int i = 0; i < baton->hunk->numLines; ++i) { git_diff_line *storeLine = (git_diff_line *)malloc(sizeof(git_diff_line)); storeLine->origin = baton->hunk->lines->at(i)->origin; @@ -130,7 +130,14 @@ void ConvenientHunk::LinesWorker::Execute() { } } -void ConvenientHunk::LinesWorker::HandleErrorCallback() {} +void ConvenientHunk::LinesWorker::HandleErrorCallback() { + while (!baton->lines->empty()) { + free(baton->lines->back()); + baton->lines->pop_back(); + } + + delete baton->lines; +} void ConvenientHunk::LinesWorker::HandleOKCallback() { unsigned int size = baton->lines->size(); diff --git a/generate/templates/manual/src/convenient_patch.cc b/generate/templates/manual/src/convenient_patch.cc index 47044e9dd..99096a390 100644 --- a/generate/templates/manual/src/convenient_patch.cc +++ b/generate/templates/manual/src/convenient_patch.cc @@ -215,6 +215,8 @@ NAN_METHOD(ConvenientPatch::Hunks) { HunksBaton *baton = new HunksBaton(); baton->patch = Nan::ObjectWrap::Unwrap(info.This())->GetValue(); + baton->hunks = new std::vector; + baton->hunks->reserve(baton->patch->numHunks); Nan::Callback *callback = new Nan::Callback(Local::Cast(info[0])); HunksWorker *worker = new HunksWorker(baton, callback); @@ -232,9 +234,6 @@ nodegit::LockMaster ConvenientPatch::HunksWorker::AcquireLocks() { void ConvenientPatch::HunksWorker::Execute() { // copy hunks - baton->hunks = new std::vector; - baton->hunks->reserve(baton->patch->numHunks); - for (unsigned int i = 0; i < baton->patch->numHunks; ++i) { HunkData *hunkData = new HunkData(); hunkData->numLines = baton->patch->hunks->at(i)->numLines; @@ -263,7 +262,19 @@ void ConvenientPatch::HunksWorker::Execute() { } } -void ConvenientPatch::HunksWorker::HandleErrorCallback() {} +void ConvenientPatch::HunksWorker::HandleErrorCallback() { + while (!baton->hunks->empty()) { + HunkData *hunk = baton->hunks->back(); + baton->hunks->pop_back(); + + while (!hunk->lines->empty()) { + free(hunk->lines->back()); + hunk->lines->pop_back(); + } + } + + delete baton->hunks; +} void ConvenientPatch::HunksWorker::HandleOKCallback() { unsigned int size = baton->hunks->size(); diff --git a/generate/templates/manual/src/filter_registry.cc b/generate/templates/manual/src/filter_registry.cc index 418c1142e..c9d9b5ffd 100644 --- a/generate/templates/manual/src/filter_registry.cc +++ b/generate/templates/manual/src/filter_registry.cc @@ -105,6 +105,8 @@ void GitFilterRegistry::RegisterWorker::HandleErrorCallback() { free((void *)baton->error); } + free(baton->filter_name); + delete baton; } @@ -147,6 +149,8 @@ void GitFilterRegistry::RegisterWorker::HandleOKCallback() { callback->Call(0, NULL, async_resource); } + free(baton->filter_name); + delete baton; } @@ -208,6 +212,8 @@ void GitFilterRegistry::UnregisterWorker::HandleErrorCallback() { free((void *)baton->error); } + free(baton->filter_name); + delete baton; } @@ -253,5 +259,7 @@ void GitFilterRegistry::UnregisterWorker::HandleOKCallback() { callback->Call(0, NULL, async_resource); } + free(baton->filter_name); + delete baton; } From e4a9092519536499d673d8fe261f0b9b478e727e Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Thu, 3 Sep 2020 10:27:47 -0700 Subject: [PATCH 24/40] Enable module from workers --- generate/templates/templates/nodegit.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate/templates/templates/nodegit.cc b/generate/templates/templates/nodegit.cc index e174ae27b..45ce409fb 100644 --- a/generate/templates/templates/nodegit.cc +++ b/generate/templates/templates/nodegit.cc @@ -108,4 +108,4 @@ NAN_MODULE_INIT(init) { nodegit::LockMaster::InitializeContext(); } -NODE_MODULE(nodegit, init) +NAN_MODULE_WORKER_ENABLED(nodegit, init) From 7f358d1c6e4300ab398f1854c808cd0380be75e9 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Thu, 3 Sep 2020 11:22:26 -0700 Subject: [PATCH 25/40] Test clone via worker thread --- test/tests/clone.js | 47 ++++++++++++++++++++++++++++++++++++++ test/utils/clone_worker.js | 19 +++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 test/utils/clone_worker.js diff --git a/test/tests/clone.js b/test/tests/clone.js index 965d78bc7..ab40e4ffc 100644 --- a/test/tests/clone.js +++ b/test/tests/clone.js @@ -1,3 +1,4 @@ +var { Worker } = require("worker_threads"); var path = require("path"); var assert = require("assert"); var fse = require("fs-extra"); @@ -54,6 +55,52 @@ describe("Clone", function() { }); }); + it("can clone with https via worker thread", function(done) { + const workerPath = path.join(__dirname, "../utils/clone_worker.js"); + const worker = new Worker(workerPath, { + workerData: { + clonePath, + url: "https://github.com/nodegit/test.git" + } + }); + worker.on("message", (success) => { + if (success) { + done(); + } else { + assert.fail(); + } + }); + worker.on("error", () => assert.fail()); + worker.on("exit", (code) => { + if (code !== 0) { + assert.fail(); + } + }); + }); + + for (let i = 0; i < 5; ++i) { + it(`can kill worker thread while cloning #${i}`, function(done) { // jshint ignore:line + const workerPath = path.join(__dirname, "../utils/clone_worker.js"); + const worker = new Worker(workerPath, { + workerData: { + clonePath, + url: "https://github.com/nodegit/test.git" + } + }); + worker.on("error", () => assert.fail()); + worker.on("message", () => assert.fail()); + worker.on("exit", (code) => { + if (code === 1) { + done(); + } else { + assert.fail(); + } + }); + + setTimeout(() => { worker.terminate(); }, 500); + }); + } + it("can clone twice with https using same config object", function() { var test = this; var url = "https://github.com/nodegit/test.git"; diff --git a/test/utils/clone_worker.js b/test/utils/clone_worker.js new file mode 100644 index 000000000..18a2c68ec --- /dev/null +++ b/test/utils/clone_worker.js @@ -0,0 +1,19 @@ +const { parentPort, workerData } = require("worker_threads"); +const assert = require("assert"); +const NodeGit = require("../../"); + +const { clonePath, url } = workerData; +const opts = { + fetchOpts: { + callbacks: { + certificateCheck: () => 0 + } + } +}; + +return NodeGit.Clone(url, clonePath, opts).then(repo => { + assert.ok(repo instanceof NodeGit.Repository); + parentPort.postMessage(true); +}).catch(() => { + parentPort.postMessage(false); +}); From 7c50ce5f6c8efeb9864c469994eede58c8d297a4 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Thu, 3 Sep 2020 11:24:27 -0700 Subject: [PATCH 26/40] Allow nodegit to run via worker thread or web worker --- generate/templates/templates/nodegit.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/generate/templates/templates/nodegit.js b/generate/templates/templates/nodegit.js index 8009150ef..bb9739ac9 100644 --- a/generate/templates/templates/nodegit.js +++ b/generate/templates/templates/nodegit.js @@ -8,10 +8,6 @@ try { var rawApi; -if (worker && (!worker.isMainThread || typeof importScripts === "function")) { - throw new Error("NodeGit is currently not safe to run in a worker thread or web worker"); // jshint ignore:line -} - // Attempt to load the production release first, if it fails fall back to the // debug release. try { From f82e10ccbc3ce7afe1109e1283b21aa344a272bc Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Thu, 3 Sep 2020 13:27:53 -0700 Subject: [PATCH 27/40] More reliable worker thread test --- test/tests/clone.js | 47 ---------------------- test/tests/worker.js | 81 ++++++++++++++++++++++++++++++++++++++ test/utils/clone_worker.js | 19 --------- test/utils/worker.js | 38 ++++++++++++++++++ 4 files changed, 119 insertions(+), 66 deletions(-) create mode 100644 test/tests/worker.js delete mode 100644 test/utils/clone_worker.js create mode 100644 test/utils/worker.js diff --git a/test/tests/clone.js b/test/tests/clone.js index ab40e4ffc..965d78bc7 100644 --- a/test/tests/clone.js +++ b/test/tests/clone.js @@ -1,4 +1,3 @@ -var { Worker } = require("worker_threads"); var path = require("path"); var assert = require("assert"); var fse = require("fs-extra"); @@ -55,52 +54,6 @@ describe("Clone", function() { }); }); - it("can clone with https via worker thread", function(done) { - const workerPath = path.join(__dirname, "../utils/clone_worker.js"); - const worker = new Worker(workerPath, { - workerData: { - clonePath, - url: "https://github.com/nodegit/test.git" - } - }); - worker.on("message", (success) => { - if (success) { - done(); - } else { - assert.fail(); - } - }); - worker.on("error", () => assert.fail()); - worker.on("exit", (code) => { - if (code !== 0) { - assert.fail(); - } - }); - }); - - for (let i = 0; i < 5; ++i) { - it(`can kill worker thread while cloning #${i}`, function(done) { // jshint ignore:line - const workerPath = path.join(__dirname, "../utils/clone_worker.js"); - const worker = new Worker(workerPath, { - workerData: { - clonePath, - url: "https://github.com/nodegit/test.git" - } - }); - worker.on("error", () => assert.fail()); - worker.on("message", () => assert.fail()); - worker.on("exit", (code) => { - if (code === 1) { - done(); - } else { - assert.fail(); - } - }); - - setTimeout(() => { worker.terminate(); }, 500); - }); - } - it("can clone twice with https using same config object", function() { var test = this; var url = "https://github.com/nodegit/test.git"; diff --git a/test/tests/worker.js b/test/tests/worker.js new file mode 100644 index 000000000..c7aed3162 --- /dev/null +++ b/test/tests/worker.js @@ -0,0 +1,81 @@ +const { Worker } = require("worker_threads"); +const path = require("path"); +const assert = require("assert"); +const fse = require("fs-extra"); +const local = path.join.bind(path, __dirname); + +describe("Worker", function() { + const clonePath = local("../repos/clone"); + + // Set a reasonable timeout here now that our repository has grown. + this.timeout(30000); + + beforeEach(function() { + return fse.remove(clonePath).catch(function(err) { + console.log(err); + + throw err; + }); + }); + + it("can perform basic functionality via worker thread", function(done) { + const workerPath = local("../utils/worker.js"); + const worker = new Worker(workerPath, { + workerData: { + clonePath, + url: "https://github.com/nodegit/test.git" + } + }); + worker.on("message", (message) => { + switch (message) { + case "init": + break; + case "success": + done(); + break; + case "failure": + assert.fail(); + break; + } + }); + worker.on("error", () => assert.fail()); + worker.on("exit", (code) => { + if (code !== 0) { + assert.fail(); + } + }); + }); + + for (let i = 0; i < 5; ++i) { + it(`can kill worker thread while in use #${i}`, function(done) { // jshint ignore:line + const workerPath = local("../utils/worker.js"); + const worker = new Worker(workerPath, { + workerData: { + clonePath, + url: "https://github.com/nodegit/test.git" + } + }); + worker.on("message", (message) => { + switch (message) { + case "init": + setTimeout(() => { worker.terminate(); }, 500); + break; + case "success": + assert.fail(); + break; + case "failure": + assert.fail(); + break; + } + }); + worker.on("error", () => assert.fail()); + worker.on("exit", (code) => { + if (code === 1) { + done(); + } else { + assert.fail(); + } + }); + }); + } +}); diff --git a/test/utils/clone_worker.js b/test/utils/clone_worker.js deleted file mode 100644 index 18a2c68ec..000000000 --- a/test/utils/clone_worker.js +++ /dev/null @@ -1,19 +0,0 @@ -const { parentPort, workerData } = require("worker_threads"); -const assert = require("assert"); -const NodeGit = require("../../"); - -const { clonePath, url } = workerData; -const opts = { - fetchOpts: { - callbacks: { - certificateCheck: () => 0 - } - } -}; - -return NodeGit.Clone(url, clonePath, opts).then(repo => { - assert.ok(repo instanceof NodeGit.Repository); - parentPort.postMessage(true); -}).catch(() => { - parentPort.postMessage(false); -}); diff --git a/test/utils/worker.js b/test/utils/worker.js new file mode 100644 index 000000000..6f2d21840 --- /dev/null +++ b/test/utils/worker.js @@ -0,0 +1,38 @@ +const { + isMainThread, + parentPort, + workerData +} = require("worker_threads"); +const assert = require("assert"); +const NodeGit = require("../../"); + +if (isMainThread) { + throw new Error("Must be run via worker thread"); +} + +parentPort.postMessage("init"); + +const { clonePath, url } = workerData; +const opts = { + fetchOpts: { + callbacks: { + certificateCheck: () => 0 + } + } +}; + +let repository; +return NodeGit.Clone(url, clonePath, opts).then((_repository) => { + repository = _repository; + assert.ok(repository instanceof NodeGit.Repository); + return repository.index(); +}).then((index) => { + assert.ok(index instanceof NodeGit.Index); + return repository.getRemoteNames(); +}).then((remotes) => { + assert.ok(Array.isArray(remotes)); + return repository.getCurrentBranch(); +}).then((branch) => { + assert.ok(branch instanceof NodeGit.Reference); + parentPort.postMessage("success"); +}).catch(() => parentPort.postMessage("failure")); From 8a5ee942660430bf462cda717c538df38ec8e899 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Tue, 8 Sep 2020 11:44:29 -0700 Subject: [PATCH 28/40] Remove duplicate locks We already lock in AcquireLocks --- generate/templates/manual/repository/refresh_references.cc | 1 - generate/templates/manual/src/filter_registry.cc | 2 -- 2 files changed, 3 deletions(-) diff --git a/generate/templates/manual/repository/refresh_references.cc b/generate/templates/manual/repository/refresh_references.cc index 6257b26f7..c9d1353c9 100644 --- a/generate/templates/manual/repository/refresh_references.cc +++ b/generate/templates/manual/repository/refresh_references.cc @@ -420,7 +420,6 @@ void GitRepository::RefreshReferencesWorker::Execute() { giterr_clear(); - nodegit::LockMaster lockMaster(true, baton->repo); git_repository *repo = baton->repo; RefreshReferencesData *refreshData = (RefreshReferencesData *)baton->out; git_odb *odb; diff --git a/generate/templates/manual/src/filter_registry.cc b/generate/templates/manual/src/filter_registry.cc index c9d9b5ffd..734f20c1a 100644 --- a/generate/templates/manual/src/filter_registry.cc +++ b/generate/templates/manual/src/filter_registry.cc @@ -86,7 +86,6 @@ void GitFilterRegistry::RegisterWorker::Execute() { git_error_clear(); { - nodegit::LockMaster lockMaster(/*asyncAction: */true, baton->filter_name, baton->filter); int result = git_filter_register(baton->filter_name, baton->filter, baton->filter_priority); baton->error_code = result; @@ -193,7 +192,6 @@ void GitFilterRegistry::UnregisterWorker::Execute() { git_error_clear(); { - nodegit::LockMaster lockMaster(/*asyncAction: */true, baton->filter_name); int result = git_filter_unregister(baton->filter_name); baton->error_code = result; From aaa34eee628351e48fc987c9f6519865972c71e3 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Thu, 10 Sep 2020 13:27:53 -0700 Subject: [PATCH 29/40] Verify context before running cppCallback --- .../manual/include/nodegit_wrapper.h | 7 +++ .../templates/manual/include/thread_pool.h | 7 ++- generate/templates/manual/src/context.cc | 2 +- .../templates/manual/src/nodegit_wrapper.cc | 6 ++- generate/templates/manual/src/thread_pool.cc | 53 ++++++++++++++----- .../templates/partials/field_accessors.cc | 9 +++- 6 files changed, 65 insertions(+), 19 deletions(-) diff --git a/generate/templates/manual/include/nodegit_wrapper.h b/generate/templates/manual/include/nodegit_wrapper.h index b48e956d1..3d8f5a089 100644 --- a/generate/templates/manual/include/nodegit_wrapper.h +++ b/generate/templates/manual/include/nodegit_wrapper.h @@ -14,6 +14,10 @@ // static const bool isFreeable // static void free(cType *raw) - frees the object using freeFunctionName +namespace nodegit { + class Context; +} + template class NodeGitWrapper : public Nan::ObjectWrap { public: @@ -29,6 +33,9 @@ class NodeGitWrapper : public Nan::ObjectWrap { // (and through a method) instead of changing selfFreeing, but that's // a separate issue. bool selfFreeing; + + const nodegit::Context *nodegitContext = nullptr; + protected: cType *raw; diff --git a/generate/templates/manual/include/thread_pool.h b/generate/templates/manual/include/thread_pool.h index 74ed09bdf..e40ad2bb6 100644 --- a/generate/templates/manual/include/thread_pool.h +++ b/generate/templates/manual/include/thread_pool.h @@ -9,6 +9,7 @@ #include "async_worker.h" namespace nodegit { + class Context; class ThreadPoolImpl; class ThreadPool { @@ -20,7 +21,7 @@ namespace nodegit { // Initializes thread pool and spins up the requested number of threads // The provided loop will be used for completion callbacks, whenever // queued work is completed - ThreadPool(int numberOfThreads, uv_loop_t *loop); + ThreadPool(int numberOfThreads, uv_loop_t *loop, nodegit::Context *context); ~ThreadPool(); @@ -35,6 +36,10 @@ namespace nodegit { // when scheduling work on the JS thread. static Nan::AsyncResource *GetCurrentAsyncResource(); + // Same as GetCurrentAsyncResource, except used to ensure callbacks occur + // in the correct context. + static const nodegit::Context *GetCurrentContext(); + // Queues a callback on the loop provided in the constructor static void PostCallbackEvent(OnPostCallbackFn onPostCallback); diff --git a/generate/templates/manual/src/context.cc b/generate/templates/manual/src/context.cc index 16a8e6267..7152e3643 100644 --- a/generate/templates/manual/src/context.cc +++ b/generate/templates/manual/src/context.cc @@ -12,7 +12,7 @@ namespace nodegit { } Context::Context(v8::Isolate *isolate) - : isolate(isolate), threadPool(10, node::GetCurrentEventLoop(isolate)) + : isolate(isolate), threadPool(10, node::GetCurrentEventLoop(isolate), this) { Nan::HandleScope scopoe; v8::Local storage = Nan::New(); diff --git a/generate/templates/manual/src/nodegit_wrapper.cc b/generate/templates/manual/src/nodegit_wrapper.cc index a9f0483b3..2eb295a80 100644 --- a/generate/templates/manual/src/nodegit_wrapper.cc +++ b/generate/templates/manual/src/nodegit_wrapper.cc @@ -1,5 +1,6 @@ template -NodeGitWrapper::NodeGitWrapper(typename Traits::cType *raw, bool selfFreeing, v8::Local owner) { +NodeGitWrapper::NodeGitWrapper(typename Traits::cType *raw, bool selfFreeing, v8::Local owner) + : nodegitContext(nodegit::Context::GetCurrentContext()) { if (Traits::isSingleton) { ReferenceCounter::incrementCountForPointer((void *)raw); this->raw = raw; @@ -35,7 +36,8 @@ NodeGitWrapper::NodeGitWrapper(typename Traits::cType *raw, bool selfFre } template -NodeGitWrapper::NodeGitWrapper(const char *error) { +NodeGitWrapper::NodeGitWrapper(const char *error) + : nodegitContext(nodegit::Context::GetCurrentContext()) { selfFreeing = false; raw = NULL; Nan::ThrowError(error); diff --git a/generate/templates/manual/src/thread_pool.cc b/generate/templates/manual/src/thread_pool.cc index 17e8d758a..1d47a6213 100644 --- a/generate/templates/manual/src/thread_pool.cc +++ b/generate/templates/manual/src/thread_pool.cc @@ -1,4 +1,5 @@ #include +#include "../include/context.h" #include "../include/thread_pool.h" #include @@ -84,7 +85,8 @@ namespace nodegit { Executor( PostCallbackEventToOrchestratorFn postCallbackEventToOrchestrator, PostCompletedEventToOrchestratorFn postCompletedEventToOrchestrator, - TakeNextTaskFn takeNextTask + TakeNextTaskFn takeNextTask, + nodegit::Context *context ); void RunTaskLoop(); @@ -95,6 +97,8 @@ namespace nodegit { static Nan::AsyncResource *GetCurrentAsyncResource(); + static const nodegit::Context *GetCurrentContext(); + static void PostCallbackEvent(ThreadPool::OnPostCallbackFn onPostCallback); // Libgit2 will call this before it spawns a child thread. @@ -114,6 +118,7 @@ namespace nodegit { private: Nan::AsyncResource *currentAsyncResource; + nodegit::Context *currentContext; // We need to populate the executor on every thread that libgit2 // could make a callback on so that it can correctly queue callbacks // in the correct javascript context @@ -127,9 +132,11 @@ namespace nodegit { Executor::Executor( PostCallbackEventToOrchestratorFn postCallbackEventToOrchestrator, PostCompletedEventToOrchestratorFn postCompletedEventToOrchestrator, - TakeNextTaskFn takeNextTask + TakeNextTaskFn takeNextTask, + nodegit::Context *context ) : currentAsyncResource(nullptr), + currentContext(context), postCallbackEventToOrchestrator(postCallbackEventToOrchestrator), postCompletedEventToOrchestrator(postCompletedEventToOrchestrator), takeNextTask(takeNextTask), @@ -171,6 +178,16 @@ namespace nodegit { return nullptr; } + const nodegit::Context *Executor::GetCurrentContext() { + if (executor) { + return executor->currentContext; + } + + // NOTE this should always be set when a libgit2 callback is running, + // so this case should not happen. + return nullptr; + } + void Executor::PostCallbackEvent(ThreadPool::OnPostCallbackFn onPostCallback) { if (executor) { executor->postCallbackEventToOrchestrator(onPostCallback); @@ -226,7 +243,8 @@ namespace nodegit { public: OrchestratorImpl( QueueCallbackOnJSThreadFn queueCallbackOnJSThread, - TakeNextJobFn takeNextJob + TakeNextJobFn takeNextJob, + nodegit::Context *context ); void RunJobLoop(); @@ -272,7 +290,8 @@ namespace nodegit { public: Orchestrator( QueueCallbackOnJSThreadFn queueCallbackOnJSThread, - TakeNextJobFn takeNextJob + TakeNextJobFn takeNextJob, + nodegit::Context *context ); void WaitForThreadClose(); @@ -280,7 +299,8 @@ namespace nodegit { Orchestrator::OrchestratorImpl::OrchestratorImpl( QueueCallbackOnJSThreadFn queueCallbackOnJSThread, - TakeNextJobFn takeNextJob + TakeNextJobFn takeNextJob, + nodegit::Context *context ) : taskMutex(new std::mutex), executorEventsMutex(new std::mutex), @@ -291,7 +311,8 @@ namespace nodegit { executor( std::bind(&Orchestrator::OrchestratorImpl::PostCallbackEvent, this, _1), std::bind(&Orchestrator::OrchestratorImpl::PostCompletedEvent, this), - std::bind(&Orchestrator::OrchestratorImpl::TakeNextTask, this) + std::bind(&Orchestrator::OrchestratorImpl::TakeNextTask, this), + context ) {} @@ -406,9 +427,10 @@ namespace nodegit { Orchestrator::Orchestrator( QueueCallbackOnJSThreadFn queueCallbackOnJSThread, - TakeNextJobFn takeNextJob + TakeNextJobFn takeNextJob, + nodegit::Context *context ) - : impl(new OrchestratorImpl(queueCallbackOnJSThread, takeNextJob)) + : impl(new OrchestratorImpl(queueCallbackOnJSThread, takeNextJob, context)) {} void Orchestrator::WaitForThreadClose() { @@ -417,7 +439,7 @@ namespace nodegit { class ThreadPoolImpl { public: - ThreadPoolImpl(int numberOfThreads, uv_loop_t *loop); + ThreadPoolImpl(int numberOfThreads, uv_loop_t *loop, nodegit::Context *context); void QueueWorker(nodegit::AsyncWorker *worker); @@ -475,7 +497,7 @@ namespace nodegit { std::vector orchestrators; }; - ThreadPoolImpl::ThreadPoolImpl(int numberOfThreads, uv_loop_t *loop) + ThreadPoolImpl::ThreadPoolImpl(int numberOfThreads, uv_loop_t *loop, nodegit::Context *context) : isMarkedForDeletion(false), orchestratorJobMutex(new std::mutex), jsThreadCallbackMutex(new std::mutex), @@ -490,7 +512,8 @@ namespace nodegit { for (int i = 0; i < numberOfThreads; i++) { orchestrators.emplace_back( std::bind(&ThreadPoolImpl::QueueCallbackOnJSThread, this, _1, _2, _3), - std::bind(&ThreadPoolImpl::TakeNextJob, this) + std::bind(&ThreadPoolImpl::TakeNextJob, this), + context ); } } @@ -657,8 +680,8 @@ namespace nodegit { uv_close((uv_handle_t *)jsThreadCallbackAsync, nullptr); } - ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop) - : impl(new ThreadPoolImpl(numberOfThreads, loop)) + ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop, nodegit::Context *context) + : impl(new ThreadPoolImpl(numberOfThreads, loop, context)) {} ThreadPool::~ThreadPool() {} @@ -675,6 +698,10 @@ namespace nodegit { return Executor::GetCurrentAsyncResource(); } + const nodegit::Context *ThreadPool::GetCurrentContext() { + return Executor::GetCurrentContext(); + } + void ThreadPool::Shutdown() { impl->Shutdown(); } diff --git a/generate/templates/partials/field_accessors.cc b/generate/templates/partials/field_accessors.cc index 5bbe0b2f6..26f52ef4d 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -138,7 +138,9 @@ {{ cppClassName }}* instance = {{ field.name }}_getInstanceFromBaton(baton); {% if field.return.type == "void" %} - if (instance->{{ field.name }}.WillBeThrottled()) { + if (instance->nodegitContext != nodegit::ThreadPool::GetCurrentContext()) { + delete baton; + } else if (instance->{{ field.name }}.WillBeThrottled()) { delete baton; } else if (instance->{{ field.name }}.ShouldWaitForResult()) { baton->ExecuteAsync({{ field.name }}_async, {{ field.name }}_cancelAsync); @@ -150,7 +152,10 @@ {% else %} {{ field.return.type }} result; - if (instance->{{ field.name }}.WillBeThrottled()) { + if (instance->nodegitContext != nodegit::ThreadPool::GetCurrentContext()) { + result = baton->defaultResult; + delete baton; + } else if (instance->{{ field.name }}.WillBeThrottled()) { result = baton->defaultResult; delete baton; } else if (instance->{{ field.name }}.ShouldWaitForResult()) { From 69d9d4e03235a52c13a5bd52d7a5f15aa0481655 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Fri, 11 Sep 2020 10:32:15 -0700 Subject: [PATCH 30/40] Stick to c++11 for a little bit longer RHEL 7 is still fairly popular and on GCC 4.8.x --- generate/templates/manual/src/lock_master.cc | 11 +++++++---- generate/templates/templates/binding.gyp | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/generate/templates/manual/src/lock_master.cc b/generate/templates/manual/src/lock_master.cc index 72740db8e..8ad33378f 100644 --- a/generate/templates/manual/src/lock_master.cc +++ b/generate/templates/manual/src/lock_master.cc @@ -77,16 +77,19 @@ namespace nodegit { std::mutex LockMasterImpl::mapMutex; thread_local LockMasterImpl* LockMasterImpl::currentLockMaster = nullptr; - LockMaster::LockMaster(LockMaster &&other) - : impl(std::exchange(other.impl, nullptr)) - {} + LockMaster::LockMaster(LockMaster &&other) { + impl = other.impl; + other.impl = nullptr; + } LockMaster &LockMaster::operator=(LockMaster &&other) { if (&other == this) { return *this; } - impl = std::exchange(other.impl, nullptr); + impl = other.impl; + other.impl = nullptr; + return *this; } diff --git a/generate/templates/templates/binding.gyp b/generate/templates/templates/binding.gyp index 2f9acf366..d47e32f31 100644 --- a/generate/templates/templates/binding.gyp +++ b/generate/templates/templates/binding.gyp @@ -114,7 +114,7 @@ "GCC_ENABLE_CPP_EXCEPTIONS": "YES", "MACOSX_DEPLOYMENT_TARGET": "10.9", 'CLANG_CXX_LIBRARY': 'libc++', - 'CLANG_CXX_LANGUAGE_STANDARD':'c++14', + 'CLANG_CXX_LANGUAGE_STANDARD':'c++11', "WARNING_CFLAGS": [ "-Wno-unused-variable", @@ -166,7 +166,7 @@ [ "OS=='linux' or OS.endswith('bsd') or <(is_IBMi) == 1", { "cflags": [ - "-std=c++14" + "-std=c++11" ] } ], From ee1865c69eb215f9a688e54cd423b4f99c678dac Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Fri, 11 Sep 2020 11:04:35 -0700 Subject: [PATCH 31/40] Only test worker_threads if they are available --- test/tests/worker.js | 101 +++++++++++++++++++++++-------------------- 1 file changed, 54 insertions(+), 47 deletions(-) diff --git a/test/tests/worker.js b/test/tests/worker.js index c7aed3162..e141b58aa 100644 --- a/test/tests/worker.js +++ b/test/tests/worker.js @@ -1,55 +1,32 @@ -const { Worker } = require("worker_threads"); const path = require("path"); const assert = require("assert"); const fse = require("fs-extra"); const local = path.join.bind(path, __dirname); -describe("Worker", function() { - const clonePath = local("../repos/clone"); +let Worker; - // Set a reasonable timeout here now that our repository has grown. - this.timeout(30000); +try { + Worker = require("worker_threads").Worker; +} catch (e) {} - beforeEach(function() { - return fse.remove(clonePath).catch(function(err) { - console.log(err); +if (Worker) { + describe("Worker", function() { + const clonePath = local("../repos/clone"); - throw err; - }); - }); + // Set a reasonable timeout here now that our repository has grown. + this.timeout(30000); - it("can perform basic functionality via worker thread", function(done) { - const workerPath = local("../utils/worker.js"); - const worker = new Worker(workerPath, { - workerData: { - clonePath, - url: "https://github.com/nodegit/test.git" - } - }); - worker.on("message", (message) => { - switch (message) { - case "init": - break; - case "success": - done(); - break; - case "failure": - assert.fail(); - break; - } - }); - worker.on("error", () => assert.fail()); - worker.on("exit", (code) => { - if (code !== 0) { - assert.fail(); - } + beforeEach(function() { + return fse.remove(clonePath).catch(function(err) { + console.log(err); + + throw err; + }); }); - }); - for (let i = 0; i < 5; ++i) { - it(`can kill worker thread while in use #${i}`, function(done) { // jshint ignore:line + it("can perform basic functionality via worker thread", function(done) { const workerPath = local("../utils/worker.js"); - const worker = new Worker(workerPath, { + const worker = new Worker(workerPath, { workerData: { clonePath, url: "https://github.com/nodegit/test.git" @@ -58,10 +35,9 @@ describe("Worker", function() { worker.on("message", (message) => { switch (message) { case "init": - setTimeout(() => { worker.terminate(); }, 500); break; case "success": - assert.fail(); + done(); break; case "failure": assert.fail(); @@ -70,12 +46,43 @@ describe("Worker", function() { }); worker.on("error", () => assert.fail()); worker.on("exit", (code) => { - if (code === 1) { - done(); - } else { + if (code !== 0) { assert.fail(); } }); }); - } -}); + + for (let i = 0; i < 5; ++i) { + it(`can kill worker thread while in use #${i}`, function(done) { // jshint ignore:line + const workerPath = local("../utils/worker.js"); + const worker = new Worker(workerPath, { + workerData: { + clonePath, + url: "https://github.com/nodegit/test.git" + } + }); + worker.on("message", (message) => { + switch (message) { + case "init": + setTimeout(() => { worker.terminate(); }, 500); + break; + case "success": + assert.fail(); + break; + case "failure": + assert.fail(); + break; + } + }); + worker.on("error", () => assert.fail()); + worker.on("exit", (code) => { + if (code === 1) { + done(); + } else { + assert.fail(); + } + }); + }); + } + }); +} From a21efa66a2da6a5f21b836854e4190bb5c6d76dc Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Fri, 11 Sep 2020 15:47:08 -0700 Subject: [PATCH 32/40] Threadpool shutdown uv_close asynchronously Windows fails to shutdown synchronously, so shutdown asynchronously. This is only supported by node 14.10+ currently. Should be backported to LTS releases shortly --- generate/templates/manual/include/context.h | 10 +++++++ .../templates/manual/include/thread_pool.h | 2 +- generate/templates/manual/src/context.cc | 27 ++++++++++++------- generate/templates/manual/src/thread_pool.cc | 12 ++++----- 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/generate/templates/manual/include/context.h b/generate/templates/manual/include/context.h index 1027622ec..d34fc13da 100644 --- a/generate/templates/manual/include/context.h +++ b/generate/templates/manual/include/context.h @@ -2,6 +2,7 @@ #define NODEGIT_CONTEXT #include +#include #include #include #include @@ -27,11 +28,20 @@ namespace nodegit { void ShutdownThreadPool(); + struct AsyncCleanupData { + Context *context; + node::AsyncCleanupHookHandle handle; + void (*doneCallback)(void*); + void *doneData; + }; + private: v8::Isolate *isolate; ThreadPool threadPool; + std::unique_ptr asyncCleanupData; + // This map contains persistent handles that need to be cleaned up // after the context has been torn down. // Often this is used as a context-aware storage cell for `*::InitializeComponent` diff --git a/generate/templates/manual/include/thread_pool.h b/generate/templates/manual/include/thread_pool.h index e40ad2bb6..394e7a46b 100644 --- a/generate/templates/manual/include/thread_pool.h +++ b/generate/templates/manual/include/thread_pool.h @@ -46,7 +46,7 @@ namespace nodegit { // Called once at libgit2 initialization to setup contracts with libgit2 static void InitializeGlobal(); - // Will wait for all threads to terminate before returning + // Will asynchronously shutdown the thread pool // It will also clean up any resources that the thread pool is keeping alive void Shutdown(); diff --git a/generate/templates/manual/src/context.cc b/generate/templates/manual/src/context.cc index 7152e3643..5fdb052f6 100644 --- a/generate/templates/manual/src/context.cc +++ b/generate/templates/manual/src/context.cc @@ -1,28 +1,35 @@ #include "../include/context.h" -namespace nodegit { - std::map Context::contexts; - - static void CleanupContext(void *data) { - Context *context = static_cast(data); +namespace { + void AsyncCleanupContext(void *data, void(*uvCallback)(void*), void *uvCallbackData) { + auto asyncCleanupData = static_cast(data); + asyncCleanupData->doneCallback = uvCallback; + asyncCleanupData->doneData = uvCallbackData; - context->ShutdownThreadPool(); - - delete context; + asyncCleanupData->context->ShutdownThreadPool(); } +} + +namespace nodegit { + std::map Context::contexts; Context::Context(v8::Isolate *isolate) - : isolate(isolate), threadPool(10, node::GetCurrentEventLoop(isolate), this) + : isolate(isolate) + , threadPool(10, node::GetCurrentEventLoop(isolate), this) + , asyncCleanupData(new Context::AsyncCleanupData()) { Nan::HandleScope scopoe; v8::Local storage = Nan::New(); persistentStorage.Reset(storage); contexts[isolate] = this; - node::AddEnvironmentCleanupHook(isolate, CleanupContext, this); + asyncCleanupData->context = this; + asyncCleanupData->handle = node::AddEnvironmentCleanupHook(isolate, AsyncCleanupContext, asyncCleanupData.get()); } Context::~Context() { contexts.erase(isolate); + + asyncCleanupData->doneCallback(asyncCleanupData->doneData); } Context *Context::GetCurrentContext() { diff --git a/generate/templates/manual/src/thread_pool.cc b/generate/templates/manual/src/thread_pool.cc index 1d47a6213..73d3a3719 100644 --- a/generate/templates/manual/src/thread_pool.cc +++ b/generate/templates/manual/src/thread_pool.cc @@ -457,6 +457,7 @@ namespace nodegit { private: bool isMarkedForDeletion; + nodegit::Context *currentContext; struct JSThreadCallback { JSThreadCallback(ThreadPool::Callback callback, ThreadPool::Callback cancelCallback, bool isWork) @@ -499,6 +500,7 @@ namespace nodegit { ThreadPoolImpl::ThreadPoolImpl(int numberOfThreads, uv_loop_t *loop, nodegit::Context *context) : isMarkedForDeletion(false), + currentContext(context), orchestratorJobMutex(new std::mutex), jsThreadCallbackMutex(new std::mutex), jsThreadCallbackAsync(new uv_async_t) @@ -672,12 +674,10 @@ namespace nodegit { jsThreadCallbackQueue.pop(); } - // NOTE We are deliberately leaking this pointer because `async` cleanup in - // node has not completely landed yet. Trying to cleanup this pointer - // is probably not worth the fight as it's very little memory lost per context - // When all LTS versions of node and Electron support async cleanup, we should - // be heading back to cleanup this - uv_close((uv_handle_t *)jsThreadCallbackAsync, nullptr); + uv_close(reinterpret_cast(jsThreadCallbackAsync), [](uv_handle_t *handle) { + auto threadPoolImpl = static_cast(handle->data); + delete threadPoolImpl->currentContext; + }); } ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop, nodegit::Context *context) From 130560b432539c1b4b740b26c1150d51a6756234 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Fri, 11 Sep 2020 15:47:43 -0700 Subject: [PATCH 33/40] Fix misc warnings --- generate/templates/manual/patches/convenient_patches.cc | 2 +- generate/templates/manual/src/async_baton.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/generate/templates/manual/patches/convenient_patches.cc b/generate/templates/manual/patches/convenient_patches.cc index af7a875ea..2e6f1ae92 100644 --- a/generate/templates/manual/patches/convenient_patches.cc +++ b/generate/templates/manual/patches/convenient_patches.cc @@ -36,7 +36,7 @@ void GitPatch::ConvenientFromDiffWorker::Execute() { std::vector patchesToBeFreed; - for (int i = 0; i < git_diff_num_deltas(baton->diff); ++i) { + for (std::size_t i = 0; i < git_diff_num_deltas(baton->diff); ++i) { git_patch *nextPatch; int result = git_patch_from_diff(&nextPatch, baton->diff, i); diff --git a/generate/templates/manual/src/async_baton.cc b/generate/templates/manual/src/async_baton.cc index 2bcd0ea82..f21e0f709 100644 --- a/generate/templates/manual/src/async_baton.cc +++ b/generate/templates/manual/src/async_baton.cc @@ -37,7 +37,7 @@ namespace nodegit { }; ThreadPool::PostCallbackEvent( - [this, jsCallback, cancelCallback]( + [jsCallback, cancelCallback]( ThreadPool::QueueCallbackFn queueCallback, ThreadPool::Callback callbackCompleted ) -> ThreadPool::Callback { From 9d5a42bf7daaa83d5badfbf46892eeabc5b8dd24 Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Fri, 11 Sep 2020 15:51:19 -0700 Subject: [PATCH 34/40] Test on node 14 only until backport of required node PR PR: https://github.com/nodejs/node/pull/34819 --- .github/workflows/tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2f2f782a3..5e2ac9ee8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,8 @@ jobs: name: "*nix Tests" strategy: matrix: - node: [10, 12, 14] + # TODO wait for https://github.com/nodejs/node/pull/34819 backport to 10 + 12 + node: [14] os: [ubuntu-16.04, macOS-10.15] runs-on: ${{ matrix.os }} steps: @@ -72,7 +73,8 @@ jobs: name: Windows Tests strategy: matrix: - node: [10, 12, 14] + # TODO wait for https://github.com/nodejs/node/pull/34819 backport to 10 + 12 + node: [14] arch: [x86, x64] runs-on: windows-2016 steps: From 01765fb3fd8204eec46187758ecd6ecfc260589b Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Fri, 11 Sep 2020 16:13:24 -0700 Subject: [PATCH 35/40] Don't leak jsThreadCallbackAsync --- generate/templates/manual/src/thread_pool.cc | 23 ++++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/generate/templates/manual/src/thread_pool.cc b/generate/templates/manual/src/thread_pool.cc index 73d3a3719..f43411d0c 100644 --- a/generate/templates/manual/src/thread_pool.cc +++ b/generate/templates/manual/src/thread_pool.cc @@ -493,7 +493,7 @@ namespace nodegit { // completion and async callbacks to be performed on the loop std::queue jsThreadCallbackQueue; std::unique_ptr jsThreadCallbackMutex; - uv_async_t *jsThreadCallbackAsync; + uv_async_t jsThreadCallbackAsync; std::vector orchestrators; }; @@ -502,12 +502,11 @@ namespace nodegit { : isMarkedForDeletion(false), currentContext(context), orchestratorJobMutex(new std::mutex), - jsThreadCallbackMutex(new std::mutex), - jsThreadCallbackAsync(new uv_async_t) + jsThreadCallbackMutex(new std::mutex) { - uv_async_init(loop, jsThreadCallbackAsync, RunLoopCallbacks); - jsThreadCallbackAsync->data = this; - uv_unref((uv_handle_t *)jsThreadCallbackAsync); + uv_async_init(loop, &jsThreadCallbackAsync, RunLoopCallbacks); + jsThreadCallbackAsync.data = this; + uv_unref((uv_handle_t *)&jsThreadCallbackAsync); workInProgressCount = 0; @@ -524,7 +523,7 @@ namespace nodegit { std::lock_guard lock(*orchestratorJobMutex); // there is work on the thread pool - reference the handle so // node doesn't terminate - uv_ref((uv_handle_t *)jsThreadCallbackAsync); + uv_ref((uv_handle_t *)&jsThreadCallbackAsync); orchestratorJobQueue.emplace(new Orchestrator::AsyncWorkJob(worker)); workInProgressCount++; orchestratorJobCondition.notify_one(); @@ -563,7 +562,7 @@ namespace nodegit { // we only trigger RunLoopCallbacks via the jsThreadCallbackAsync handle if the queue // was empty. Otherwise, we depend on RunLoopCallbacks to re-trigger itself if (queueWasEmpty) { - uv_async_send(jsThreadCallbackAsync); + uv_async_send(&jsThreadCallbackAsync); } } @@ -587,7 +586,7 @@ namespace nodegit { lock.lock(); if (!jsThreadCallbackQueue.empty()) { - uv_async_send(jsThreadCallbackAsync); + uv_async_send(&jsThreadCallbackAsync); } // if there is no ongoing work / completion processing, node doesn't need @@ -596,7 +595,7 @@ namespace nodegit { std::lock_guard orchestratorLock(*orchestratorJobMutex); workInProgressCount--; if (!workInProgressCount) { - uv_unref((uv_handle_t *)jsThreadCallbackAsync); + uv_unref((uv_handle_t *)&jsThreadCallbackAsync); } } } @@ -628,7 +627,7 @@ namespace nodegit { // unref the jsThreadCallback for all work in progress // it will not be used after this function has completed while (workInProgressCount--) { - uv_unref((uv_handle_t *)jsThreadCallbackAsync); + uv_unref((uv_handle_t *)&jsThreadCallbackAsync); } } @@ -674,7 +673,7 @@ namespace nodegit { jsThreadCallbackQueue.pop(); } - uv_close(reinterpret_cast(jsThreadCallbackAsync), [](uv_handle_t *handle) { + uv_close(reinterpret_cast(&jsThreadCallbackAsync), [](uv_handle_t *handle) { auto threadPoolImpl = static_cast(handle->data); delete threadPoolImpl->currentContext; }); From fb1737264cf36e1c4a3bd1c96a2a0314a7001f9e Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Fri, 11 Sep 2020 17:00:37 -0700 Subject: [PATCH 36/40] Build a cleanup handle that can be deleted by last user --- generate/templates/manual/include/context.h | 27 ++++++++------ .../templates/manual/include/thread_pool.h | 3 +- generate/templates/manual/src/context.cc | 35 ++++++++++--------- generate/templates/manual/src/thread_pool.cc | 13 +++---- 4 files changed, 45 insertions(+), 33 deletions(-) diff --git a/generate/templates/manual/include/context.h b/generate/templates/manual/include/context.h index d34fc13da..84e8c22c4 100644 --- a/generate/templates/manual/include/context.h +++ b/generate/templates/manual/include/context.h @@ -12,6 +12,7 @@ #include "thread_pool.h" namespace nodegit { + class AsyncContextCleanupHandle; class Context { public: Context(v8::Isolate *isolate); @@ -26,22 +27,13 @@ namespace nodegit { void SaveToPersistent(std::string key, const v8::Local &value); - void ShutdownThreadPool(); - - struct AsyncCleanupData { - Context *context; - node::AsyncCleanupHookHandle handle; - void (*doneCallback)(void*); - void *doneData; - }; + void ShutdownThreadPool(AsyncContextCleanupHandle *cleanupHandle); private: v8::Isolate *isolate; ThreadPool threadPool; - std::unique_ptr asyncCleanupData; - // This map contains persistent handles that need to be cleaned up // after the context has been torn down. // Often this is used as a context-aware storage cell for `*::InitializeComponent` @@ -50,6 +42,21 @@ namespace nodegit { static std::map contexts; }; + + class AsyncContextCleanupHandle { + public: + ~AsyncContextCleanupHandle(); + + private: + static void AsyncCleanupContext(void *data, void (*uvCallback)(void *), void *uvCallbackData); + + friend class Context; + AsyncContextCleanupHandle(v8::Isolate *isolate, Context *context); + Context *context; + node::AsyncCleanupHookHandle handle; + void (*doneCallback)(void *); + void *doneData; + }; } #endif diff --git a/generate/templates/manual/include/thread_pool.h b/generate/templates/manual/include/thread_pool.h index 394e7a46b..e854f5495 100644 --- a/generate/templates/manual/include/thread_pool.h +++ b/generate/templates/manual/include/thread_pool.h @@ -10,6 +10,7 @@ namespace nodegit { class Context; + class AsyncContextCleanupHandle; class ThreadPoolImpl; class ThreadPool { @@ -48,7 +49,7 @@ namespace nodegit { // Will asynchronously shutdown the thread pool // It will also clean up any resources that the thread pool is keeping alive - void Shutdown(); + void Shutdown(AsyncContextCleanupHandle *cleanupHandle); private: std::unique_ptr impl; diff --git a/generate/templates/manual/src/context.cc b/generate/templates/manual/src/context.cc index 5fdb052f6..f5df2af81 100644 --- a/generate/templates/manual/src/context.cc +++ b/generate/templates/manual/src/context.cc @@ -1,35 +1,38 @@ #include "../include/context.h" -namespace { - void AsyncCleanupContext(void *data, void(*uvCallback)(void*), void *uvCallbackData) { - auto asyncCleanupData = static_cast(data); - asyncCleanupData->doneCallback = uvCallback; - asyncCleanupData->doneData = uvCallbackData; +namespace nodegit { + std::map Context::contexts; - asyncCleanupData->context->ShutdownThreadPool(); + AsyncContextCleanupHandle::AsyncContextCleanupHandle(v8::Isolate *isolate, Context *context) + : context(context), + handle(node::AddEnvironmentCleanupHook(isolate, AsyncCleanupContext, this)) + {} + + AsyncContextCleanupHandle::~AsyncContextCleanupHandle() { + delete context; + doneCallback(doneData); } -} -namespace nodegit { - std::map Context::contexts; + void AsyncContextCleanupHandle::AsyncCleanupContext(void *data, void(*uvCallback)(void*), void *uvCallbackData) { + auto cleanupHandle = static_cast(data); + cleanupHandle->doneCallback = uvCallback; + cleanupHandle->doneData = uvCallbackData; + cleanupHandle->context->ShutdownThreadPool(cleanupHandle); + } Context::Context(v8::Isolate *isolate) : isolate(isolate) , threadPool(10, node::GetCurrentEventLoop(isolate), this) - , asyncCleanupData(new Context::AsyncCleanupData()) { Nan::HandleScope scopoe; v8::Local storage = Nan::New(); persistentStorage.Reset(storage); contexts[isolate] = this; - asyncCleanupData->context = this; - asyncCleanupData->handle = node::AddEnvironmentCleanupHook(isolate, AsyncCleanupContext, asyncCleanupData.get()); + new AsyncContextCleanupHandle(isolate, this); } Context::~Context() { contexts.erase(isolate); - - asyncCleanupData->doneCallback(asyncCleanupData->doneData); } Context *Context::GetCurrentContext() { @@ -56,7 +59,7 @@ namespace nodegit { Nan::Set(storage, Nan::New(key).ToLocalChecked(), value); } - void Context::ShutdownThreadPool() { - threadPool.Shutdown(); + void Context::ShutdownThreadPool(AsyncContextCleanupHandle *cleanupHandle) { + threadPool.Shutdown(cleanupHandle); } } diff --git a/generate/templates/manual/src/thread_pool.cc b/generate/templates/manual/src/thread_pool.cc index f43411d0c..98cdbf291 100644 --- a/generate/templates/manual/src/thread_pool.cc +++ b/generate/templates/manual/src/thread_pool.cc @@ -453,7 +453,7 @@ namespace nodegit { static void RunLoopCallbacks(uv_async_t *handle); - void Shutdown(); + void Shutdown(AsyncContextCleanupHandle *cleanupHandle); private: bool isMarkedForDeletion; @@ -600,7 +600,7 @@ namespace nodegit { } } - void ThreadPoolImpl::Shutdown() { + void ThreadPoolImpl::Shutdown(AsyncContextCleanupHandle *cleanupHandle) { std::queue> cancelledJobs; std::queue cancelledCallbacks; { @@ -673,9 +673,10 @@ namespace nodegit { jsThreadCallbackQueue.pop(); } + jsThreadCallbackAsync.data = cleanupHandle; uv_close(reinterpret_cast(&jsThreadCallbackAsync), [](uv_handle_t *handle) { - auto threadPoolImpl = static_cast(handle->data); - delete threadPoolImpl->currentContext; + auto cleanupHandle = static_cast(handle->data); + delete cleanupHandle; }); } @@ -701,8 +702,8 @@ namespace nodegit { return Executor::GetCurrentContext(); } - void ThreadPool::Shutdown() { - impl->Shutdown(); + void ThreadPool::Shutdown(AsyncContextCleanupHandle *cleanupHandle) { + impl->Shutdown(cleanupHandle); } void ThreadPool::InitializeGlobal() { From 7a7e1fa3cb1e02a9cc93934aae4dfedaeb8de1ef Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Fri, 11 Sep 2020 17:18:02 -0700 Subject: [PATCH 37/40] Use a better data type for data on uv_async_t --- generate/templates/manual/include/context.h | 2 +- .../templates/manual/include/thread_pool.h | 2 +- generate/templates/manual/src/context.cc | 8 ++--- generate/templates/manual/src/thread_pool.cc | 33 ++++++++++++++----- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/generate/templates/manual/include/context.h b/generate/templates/manual/include/context.h index 84e8c22c4..ab0b256ea 100644 --- a/generate/templates/manual/include/context.h +++ b/generate/templates/manual/include/context.h @@ -27,7 +27,7 @@ namespace nodegit { void SaveToPersistent(std::string key, const v8::Local &value); - void ShutdownThreadPool(AsyncContextCleanupHandle *cleanupHandle); + void ShutdownThreadPool(std::unique_ptr cleanupHandle); private: v8::Isolate *isolate; diff --git a/generate/templates/manual/include/thread_pool.h b/generate/templates/manual/include/thread_pool.h index e854f5495..32d94a571 100644 --- a/generate/templates/manual/include/thread_pool.h +++ b/generate/templates/manual/include/thread_pool.h @@ -49,7 +49,7 @@ namespace nodegit { // Will asynchronously shutdown the thread pool // It will also clean up any resources that the thread pool is keeping alive - void Shutdown(AsyncContextCleanupHandle *cleanupHandle); + void Shutdown(std::unique_ptr cleanupHandle); private: std::unique_ptr impl; diff --git a/generate/templates/manual/src/context.cc b/generate/templates/manual/src/context.cc index f5df2af81..8d97361d3 100644 --- a/generate/templates/manual/src/context.cc +++ b/generate/templates/manual/src/context.cc @@ -14,10 +14,10 @@ namespace nodegit { } void AsyncContextCleanupHandle::AsyncCleanupContext(void *data, void(*uvCallback)(void*), void *uvCallbackData) { - auto cleanupHandle = static_cast(data); + std::unique_ptr cleanupHandle(static_cast(data)); cleanupHandle->doneCallback = uvCallback; cleanupHandle->doneData = uvCallbackData; - cleanupHandle->context->ShutdownThreadPool(cleanupHandle); + cleanupHandle->context->ShutdownThreadPool(std::move(cleanupHandle)); } Context::Context(v8::Isolate *isolate) @@ -59,7 +59,7 @@ namespace nodegit { Nan::Set(storage, Nan::New(key).ToLocalChecked(), value); } - void Context::ShutdownThreadPool(AsyncContextCleanupHandle *cleanupHandle) { - threadPool.Shutdown(cleanupHandle); + void Context::ShutdownThreadPool(std::unique_ptr cleanupHandle) { + threadPool.Shutdown(std::move(cleanupHandle)); } } diff --git a/generate/templates/manual/src/thread_pool.cc b/generate/templates/manual/src/thread_pool.cc index 98cdbf291..5fc72a903 100644 --- a/generate/templates/manual/src/thread_pool.cc +++ b/generate/templates/manual/src/thread_pool.cc @@ -453,7 +453,16 @@ namespace nodegit { static void RunLoopCallbacks(uv_async_t *handle); - void Shutdown(AsyncContextCleanupHandle *cleanupHandle); + void Shutdown(std::unique_ptr cleanupHandle); + + struct AsyncCallbackData { + AsyncCallbackData(ThreadPoolImpl *pool) + : pool(pool) + {} + + std::unique_ptr cleanupHandle; + ThreadPoolImpl *pool; + }; private: bool isMarkedForDeletion; @@ -505,7 +514,7 @@ namespace nodegit { jsThreadCallbackMutex(new std::mutex) { uv_async_init(loop, &jsThreadCallbackAsync, RunLoopCallbacks); - jsThreadCallbackAsync.data = this; + jsThreadCallbackAsync.data = new AsyncCallbackData(this); uv_unref((uv_handle_t *)&jsThreadCallbackAsync); workInProgressCount = 0; @@ -567,7 +576,10 @@ namespace nodegit { } void ThreadPoolImpl::RunLoopCallbacks(uv_async_t* handle) { - static_cast(handle->data)->RunLoopCallbacks(); + auto asyncCallbackData = static_cast(handle->data); + if (asyncCallbackData->pool) { + asyncCallbackData->pool->RunLoopCallbacks(); + } } // NOTE this should theoretically never be triggered during a cleanup operation @@ -600,7 +612,7 @@ namespace nodegit { } } - void ThreadPoolImpl::Shutdown(AsyncContextCleanupHandle *cleanupHandle) { + void ThreadPoolImpl::Shutdown(std::unique_ptr cleanupHandle) { std::queue> cancelledJobs; std::queue cancelledCallbacks; { @@ -673,10 +685,13 @@ namespace nodegit { jsThreadCallbackQueue.pop(); } - jsThreadCallbackAsync.data = cleanupHandle; + AsyncCallbackData *asyncCallbackData = static_cast(jsThreadCallbackAsync.data); + asyncCallbackData->cleanupHandle.swap(cleanupHandle); + asyncCallbackData->pool = nullptr; + uv_close(reinterpret_cast(&jsThreadCallbackAsync), [](uv_handle_t *handle) { - auto cleanupHandle = static_cast(handle->data); - delete cleanupHandle; + auto asyncCallbackData = static_cast(handle->data); + delete asyncCallbackData; }); } @@ -702,8 +717,8 @@ namespace nodegit { return Executor::GetCurrentContext(); } - void ThreadPool::Shutdown(AsyncContextCleanupHandle *cleanupHandle) { - impl->Shutdown(cleanupHandle); + void ThreadPool::Shutdown(std::unique_ptr cleanupHandle) { + impl->Shutdown(std::move(cleanupHandle)); } void ThreadPool::InitializeGlobal() { From cb7cd2644f71f5c032d02eb70d1535efdf58478f Mon Sep 17 00:00:00 2001 From: Tyler Ang-Wanek Date: Mon, 14 Sep 2020 08:21:26 -0700 Subject: [PATCH 38/40] Fix ordering issue on Windows --- generate/templates/manual/src/context.cc | 6 +++++- generate/templates/manual/src/thread_pool.cc | 2 -- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/generate/templates/manual/src/context.cc b/generate/templates/manual/src/context.cc index 8d97361d3..88ef1a0be 100644 --- a/generate/templates/manual/src/context.cc +++ b/generate/templates/manual/src/context.cc @@ -17,7 +17,11 @@ namespace nodegit { std::unique_ptr cleanupHandle(static_cast(data)); cleanupHandle->doneCallback = uvCallback; cleanupHandle->doneData = uvCallbackData; - cleanupHandle->context->ShutdownThreadPool(std::move(cleanupHandle)); + // the ordering of std::move and the call to Context::ShutdownThreadPool prohibits + // us from referring to context on cleanupHandle if we're also intending to move + // the unique_ptr into the method. + Context *context = cleanupHandle->context; + context->ShutdownThreadPool(std::move(cleanupHandle)); } Context::Context(v8::Isolate *isolate) diff --git a/generate/templates/manual/src/thread_pool.cc b/generate/templates/manual/src/thread_pool.cc index 5fc72a903..6c8e6776e 100644 --- a/generate/templates/manual/src/thread_pool.cc +++ b/generate/templates/manual/src/thread_pool.cc @@ -8,8 +8,6 @@ #include #include -#include - extern "C" { #include } From 85f55d4b9a3e27952fbff6f2a1959a7e0a9b467a Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Mon, 19 Oct 2020 09:05:34 -0700 Subject: [PATCH 39/40] Require 12.19.0+ or 14.10.0+ due to async cleanup --- .github/workflows/tests.yml | 6 ++---- package.json | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5e2ac9ee8..13dcc8538 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,8 +14,7 @@ jobs: name: "*nix Tests" strategy: matrix: - # TODO wait for https://github.com/nodejs/node/pull/34819 backport to 10 + 12 - node: [14] + node: [12, 14] os: [ubuntu-16.04, macOS-10.15] runs-on: ${{ matrix.os }} steps: @@ -73,8 +72,7 @@ jobs: name: Windows Tests strategy: matrix: - # TODO wait for https://github.com/nodejs/node/pull/34819 backport to 10 + 12 - node: [14] + node: [12, 14] arch: [x86, x64] runs-on: windows-2016 steps: diff --git a/package.json b/package.json index 18ba76965..c69caf907 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "lib": "./lib" }, "engines": { - "node": ">= 6" + "node": ">= 12.19.0 < 13 || >= 14.10.0" }, "dependencies": { "fs-extra": "^7.0.0", From 08db6fc42b144c97e6c4322b57c04510be30b78f Mon Sep 17 00:00:00 2001 From: Ian Hattendorf Date: Mon, 19 Oct 2020 11:22:50 -0700 Subject: [PATCH 40/40] Check for latest node version when running tests --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 13dcc8538..fbd1bafc9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -35,6 +35,7 @@ jobs: uses: actions/setup-node@master with: node-version: ${{ matrix.node }} + check-latest: true - name: Install Dependencies for Ubuntu if: startsWith(matrix.os, 'ubuntu') @@ -90,6 +91,7 @@ jobs: uses: implausible/setup-node@feature/expose-architecture-override with: node-version: ${{ matrix.node }} + check-latest: true node-arch: ${{ matrix.arch }} - name: Install