diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2f2f782a3..fbd1bafc9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: name: "*nix Tests" strategy: matrix: - node: [10, 12, 14] + node: [12, 14] os: [ubuntu-16.04, macOS-10.15] runs-on: ${{ matrix.os }} steps: @@ -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') @@ -72,7 +73,7 @@ jobs: name: Windows Tests strategy: matrix: - node: [10, 12, 14] + node: [12, 14] arch: [x86, x64] runs-on: windows-2016 steps: @@ -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 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/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/manual/clone/clone.cc b/generate/templates/manual/clone/clone.cc index a7ac262dc..a75ecb134 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; @@ -86,37 +86,60 @@ 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()); +void GitClone::CloneWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); } + + free((void *)baton->error); } + + git_repository_free(baton->out); + + free((void*)baton->url); + free((void*)baton->local_path); + + delete baton; } void GitClone::CloneWorker::HandleOKCallback() { @@ -207,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 e96d0cc7f..acad88e54 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; @@ -67,32 +67,48 @@ 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()); +void GitCommit::ExtractSignatureWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); } + + free((void *)baton->error); } + + git_buf_dispose(&baton->signature); + git_buf_dispose(&baton->signed_data); + + free(baton->field); + + 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 fd02a44e6..253d61e06 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; @@ -103,26 +103,48 @@ 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()); +void GitFilterList::LoadWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); } + + free((void *)baton->error); } + + git_filter_list_free(baton->filters); + + free((void *)baton->path); + + delete baton; } void GitFilterList::LoadWorker::HandleOKCallback() { @@ -133,7 +155,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( @@ -241,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 57c2a07f7..2ca9f5f47 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; @@ -23,25 +23,41 @@ 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()); +void GitFilterSource::RepoWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); } + + free((void *)baton->error); } + + git_repository_free(baton->out); + + delete baton; } void GitFilterSource::RepoWorker::HandleOKCallback() { diff --git a/generate/templates/manual/include/async_baton.h b/generate/templates/manual/include/async_baton.h index f8373cd0d..54f580f94 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, AsyncCallback asyncCancelCb, 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::AsyncCallback asyncCancelCb, AsyncBaton::CompletionCallback onCompletion = nullptr) { + result = 0; + ExecuteAsyncPerform(asyncCallback, asyncCancelCb, onCompletion); + return result; } - } + }; - return; - } -}; + class AsyncBatonWithNoResult : public AsyncBaton { + public: + void ExecuteAsync(AsyncBaton::AsyncCallback asyncCallback, AsyncBaton::AsyncCallback asyncCancelCb, AsyncBaton::CompletionCallback onCompletion = nullptr) { + ExecuteAsyncPerform(asyncCallback, asyncCancelCb, 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..13005040a --- /dev/null +++ b/generate/templates/manual/include/async_worker.h @@ -0,0 +1,33 @@ +#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); + + // 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(); + + bool GetIsCancelled() const; + + private: + bool isCancelled = false; + }; +} + +#endif diff --git a/generate/templates/manual/include/context.h b/generate/templates/manual/include/context.h new file mode 100644 index 000000000..ab0b256ea --- /dev/null +++ b/generate/templates/manual/include/context.h @@ -0,0 +1,62 @@ +#ifndef NODEGIT_CONTEXT +#define NODEGIT_CONTEXT + +#include +#include +#include +#include +#include +#include + +#include "async_worker.h" +#include "thread_pool.h" + +namespace nodegit { + class AsyncContextCleanupHandle; + class Context { + public: + Context(v8::Isolate *isolate); + + ~Context(); + + static Context *GetCurrentContext(); + + v8::Local GetFromPersistent(std::string key); + + void QueueWorker(nodegit::AsyncWorker *worker); + + void SaveToPersistent(std::string key, const v8::Local &value); + + void ShutdownThreadPool(std::unique_ptr cleanupHandle); + + 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; + }; + + 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/convenient_hunk.h b/generate/templates/manual/include/convenient_hunk.h index 37e9ab111..dbdfb5c79 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,18 @@ 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 HandleErrorCallback(); 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..148c14ff2 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,18 @@ 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 HandleErrorCallback(); 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..c329b33e7 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,29 @@ 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 HandleErrorCallback(); 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 HandleErrorCallback(); 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 fde38825b..54ca0e64a 100644 --- a/generate/templates/manual/include/lock_master.h +++ b/generate/templates/manual/include/lock_master.h @@ -3,199 +3,176 @@ #include -class LockMasterImpl; - -class LockMaster { -public: - enum Status { - Disabled = 0, - EnabledForAsyncOnly, - Enabled - }; - -private: - static Status status; - - LockMasterImpl *impl; - - template - void AddLocks(const T *t) { - // by default, don't lock anything - } +namespace nodegit { + class LockMasterImpl; - // 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((status == Disabled) || ((status == EnabledForAsyncOnly) && !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 Initialize(); + // 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(); + } + }; - // Enables the thread safety system - static void Enable() { - status = Enabled; - } + static void InitializeGlobal(); + static void InitializeContext(); + }; - static void SetStatus(Status status) { - LockMaster::status = status; - } - static void Disable() { - status = Disabled; + template<> inline void LockMaster::AddLocks(const git_repository *repo) { + // when using a repo, lock the repo + ObjectToLock(repo); } - static Status GetStatus() { - return status; + 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); } - // 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(); -}; - - -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_commit *commit) { + // when using a commit, lock the repo + const void *owner = git_commit_owner(commit); + ObjectToLock(owner); } - 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); + // ... 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) } -// ... 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..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; @@ -37,12 +44,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/thread_pool.h b/generate/templates/manual/include/thread_pool.h index 8a346028d..32d94a571 100644 --- a/generate/templates/manual/include/thread_pool.h +++ b/generate/templates/manual/include/thread_pool.h @@ -1,63 +1,59 @@ #ifndef THREAD_POOL_H #define THREAD_POOL_H +#include +#include +#include #include -#include -class ThreadPool { -public: - typedef void (*Callback) (void *); +#include "async_worker.h" -private: - struct Work { - Callback workCallback; - Callback completionCallback; - void *data; +namespace nodegit { + class Context; + class AsyncContextCleanupHandle; + class ThreadPoolImpl; - Work(Callback workCallback, Callback completionCallback, void *data) - : workCallback(workCallback), completionCallback(completionCallback), data(data) { - } - }; + class ThreadPool { + public: + typedef std::function Callback; + typedef std::function QueueCallbackFn; + typedef std::function OnPostCallbackFn; - struct LoopCallback { - Callback callback; - void *data; - bool isWork; + // 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, nodegit::Context *context); - LoopCallback(Callback callback, void *data, bool isWork) - : callback(callback), data(data), isWork(isWork) { - } - }; + ~ThreadPool(); + + // 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); + + // 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(); + + // Same as GetCurrentAsyncResource, except used to ensure callbacks occur + // in the correct context. + static const nodegit::Context *GetCurrentContext(); - // work to be performed on the threadpool - std::queue workQueue; - uv_mutex_t workMutex; - uv_sem_t workSemaphore; - int workInProgressCount; - - // completion and async callbacks to be performed on the loop - std::queue loopQueue; - uv_mutex_t loopMutex; - uv_async_t loopAsync; - - static void RunEventQueue(void *threadPool); - 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); -}; + // Queues a callback on the loop provided in the constructor + static void PostCallbackEvent(OnPostCallbackFn onPostCallback); + + // Called once at libgit2 initialization to setup contracts with libgit2 + static void InitializeGlobal(); + + // Will asynchronously shutdown the thread pool + // It will also clean up any resources that the thread pool is keeping alive + void Shutdown(std::unique_ptr cleanupHandle); + + 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..2e6f1ae92 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; @@ -21,57 +21,79 @@ 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; + 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); + 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); - if (result) { - while (!patchesToBeFreed.empty()) - { - git_patch_free(patchesToBeFreed.back()); - patchesToBeFreed.pop_back(); - } + if (result) { + while (!patchesToBeFreed.empty()) + { + git_patch_free(patchesToBeFreed.back()); + patchesToBeFreed.pop_back(); + } - while (!baton->out->empty()) { - PatchDataFree(baton->out->back()); - baton->out->pop_back(); - } + while (!baton->out->empty()) { + PatchDataFree(baton->out->back()); + baton->out->pop_back(); + } - baton->error_code = result; + baton->error_code = result; - if (git_error_last() != NULL) { - baton->error = git_error_dup(git_error_last()); - } + if (git_error_last() != NULL) { + baton->error = git_error_dup(git_error_last()); + } - delete baton->out; - baton->out = NULL; + delete baton->out; + baton->out = NULL; - return; - } + return; + } - if (nextPatch != NULL) { - baton->out->push_back(createFromRaw(nextPatch)); - patchesToBeFreed.push_back(nextPatch); - } + if (nextPatch != NULL) { + baton->out->push_back(createFromRaw(nextPatch)); + patchesToBeFreed.push_back(nextPatch); } + } - while (!patchesToBeFreed.empty()) - { - git_patch_free(patchesToBeFreed.back()); - patchesToBeFreed.pop_back(); + while (!patchesToBeFreed.empty()) + { + git_patch_free(patchesToBeFreed.back()); + patchesToBeFreed.pop_back(); + } +} + +void GitPatch::ConvenientFromDiffWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); } + + free((void *)baton->error); + } + + while (!baton->out->empty()) { + PatchDataFree(baton->out->back()); + baton->out->pop_back(); } + + delete baton->out; + + delete baton; } void GitPatch::ConvenientFromDiffWorker::HandleOKCallback() { diff --git a/generate/templates/manual/remote/ls.cc b/generate/templates/manual/remote/ls.cc index 8816e0150..75a7ffed4 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; @@ -14,42 +14,55 @@ 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 - ); - - if (baton->error_code != GIT_OK) { - baton->error = git_error_dup(git_error_last()); - delete baton->out; - baton->out = NULL; - return; - } + 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; + } + + 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); +void GitRemote::ReferenceListWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); } + + free((void *)baton->error); } + + delete baton->out; + + delete baton; } void GitRemote::ReferenceListWorker::HandleOKCallback() @@ -97,4 +110,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 8f03d60e1..b868bc422 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; @@ -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; @@ -76,6 +81,26 @@ void GitRepository::GetReferencesWorker::Execute() } } +void GitRepository::GetReferencesWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + 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; +} + void GitRepository::GetReferencesWorker::HandleOKCallback() { if (baton->out != NULL) @@ -130,4 +155,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 9cad189a2..6e3a398ab 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; @@ -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)); } @@ -77,6 +83,26 @@ void GitRepository::GetRemotesWorker::Execute() } } +void GitRepository::GetRemotesWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + 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; +} + void GitRepository::GetRemotesWorker::HandleOKCallback() { if (baton->out != NULL) @@ -131,4 +157,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 71de6948c..5d3e07998 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; @@ -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); @@ -58,6 +62,25 @@ void GitRepository::GetSubmodulesWorker::Execute() } } +void GitRepository::GetSubmodulesWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + while (baton->out->size()) { + git_submodule_free(baton->out->back()); + baton->out->pop_back(); + } + + delete baton->out; + + delete baton; +} + void GitRepository::GetSubmodulesWorker::HandleOKCallback() { if (baton->out != NULL) @@ -112,4 +135,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 730afadbc..c9d1353c9 100644 --- a/generate/templates/manual/repository/refresh_references.cc +++ b/generate/templates/manual/repository/refresh_references.cc @@ -395,26 +395,31 @@ 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])); 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); git_repository *repo = baton->repo; RefreshReferencesData *refreshData = (RefreshReferencesData *)baton->out; git_odb *odb; @@ -583,12 +588,27 @@ void GitRepository::RefreshReferencesWorker::Execute() } } +void GitRepository::RefreshReferencesWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + RefreshReferencesData *refreshData = (RefreshReferencesData *)baton->out; + delete refreshData; + + delete baton; +} + 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( @@ -670,4 +690,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 bef5bc889..e99543d52 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; @@ -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(); @@ -201,6 +207,26 @@ void GitRevwalk::CommitWalkWorker::Execute() { } } +void GitRevwalk::CommitWalkWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + auto out = static_cast *>(baton->out); + while (out->size()) { + delete out->back(); + out->pop_back(); + } + + delete out; + + delete baton; +} + void GitRevwalk::CommitWalkWorker::HandleOKCallback() { if (baton->out != NULL) { std::vector *out = static_cast *>(baton->out); @@ -244,4 +270,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 002251852..989ed15c0 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; @@ -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++) @@ -66,6 +72,25 @@ void GitRevwalk::FastWalkWorker::Execute() } } +void GitRevwalk::FastWalkWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + while(!baton->out->empty()) { + free(baton->out->back()); + baton->out->pop_back(); + } + + delete baton->out; + + delete baton; +} + void GitRevwalk::FastWalkWorker::HandleOKCallback() { if (baton->out != NULL) @@ -172,4 +197,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 d8d2935df..850e5d309 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; @@ -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); @@ -418,6 +424,26 @@ void GitRevwalk::FileHistoryWalkWorker::Execute() baton->file_path = NULL; } +void GitRevwalk::FileHistoryWalkWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + 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; +} + void GitRevwalk::FileHistoryWalkWorker::HandleOKCallback() { if (baton->out != NULL) { @@ -475,4 +501,6 @@ void GitRevwalk::FileHistoryWalkWorker::HandleOKCallback() } callback->Call(0, NULL, async_resource); + + delete baton; } diff --git a/generate/templates/manual/src/async_baton.cc b/generate/templates/manual/src/async_baton.cc index 590a19c62..f21e0f709 100644 --- a/generate/templates/manual/src/async_baton.cc +++ b/generate/templates/manual/src/async_baton.cc @@ -1,5 +1,72 @@ #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, AsyncCallback asyncCancelCb, CompletionCallback onCompletion) { + auto jsCallback = [asyncCallback, this]() { + asyncCallback(this); + }; + auto cancelCallback = [asyncCancelCb, this]() { + asyncCancelCb(this); + }; + + if (onCompletion) { + this->onCompletion = [this, onCompletion]() { + onCompletion(this); + }; + + ThreadPool::PostCallbackEvent( + [jsCallback, cancelCallback]( + ThreadPool::QueueCallbackFn queueCallback, + ThreadPool::Callback callbackCompleted + ) -> ThreadPool::Callback { + queueCallback(jsCallback, cancelCallback); + callbackCompleted(); + + return []() {}; + } + ); + } else { + ThreadPool::PostCallbackEvent( + [this, jsCallback, cancelCallback]( + ThreadPool::QueueCallbackFn queueCallback, + ThreadPool::Callback callbackCompleted + ) -> ThreadPool::Callback { + this->onCompletion = callbackCompleted; + + queueCallback(jsCallback, cancelCallback); + + 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..26a07b098 --- /dev/null +++ b/generate/templates/manual/src/async_worker.cc @@ -0,0 +1,24 @@ +#include "../include/async_worker.h" + +namespace nodegit { + AsyncWorker::AsyncWorker(Nan::Callback *callback, const char *resourceName) + : Nan::AsyncWorker(callback, resourceName) + {} + + void AsyncWorker::Cancel() { + isCancelled = true; + + // We use Nan::AsyncWorker's ErrorMessage flow + // to trigger `HandleErrorCallback` for cancellation + // of AsyncWork + SetErrorMessage("SHUTTING DOWN"); + } + + Nan::AsyncResource *AsyncWorker::GetAsyncResource() { + return async_resource; + } + + bool AsyncWorker::GetIsCancelled() const { + return isCancelled; + } +} diff --git a/generate/templates/manual/src/context.cc b/generate/templates/manual/src/context.cc new file mode 100644 index 000000000..88ef1a0be --- /dev/null +++ b/generate/templates/manual/src/context.cc @@ -0,0 +1,69 @@ +#include "../include/context.h" + +namespace nodegit { + std::map Context::contexts; + + AsyncContextCleanupHandle::AsyncContextCleanupHandle(v8::Isolate *isolate, Context *context) + : context(context), + handle(node::AddEnvironmentCleanupHook(isolate, AsyncCleanupContext, this)) + {} + + AsyncContextCleanupHandle::~AsyncContextCleanupHandle() { + delete context; + doneCallback(doneData); + } + + void AsyncContextCleanupHandle::AsyncCleanupContext(void *data, void(*uvCallback)(void*), void *uvCallbackData) { + std::unique_ptr cleanupHandle(static_cast(data)); + cleanupHandle->doneCallback = uvCallback; + cleanupHandle->doneData = uvCallbackData; + // 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) + : isolate(isolate) + , threadPool(10, node::GetCurrentEventLoop(isolate), this) + { + Nan::HandleScope scopoe; + v8::Local storage = Nan::New(); + persistentStorage.Reset(storage); + contexts[isolate] = this; + new AsyncContextCleanupHandle(isolate, this); + } + + Context::~Context() { + contexts.erase(isolate); + } + + Context *Context::GetCurrentContext() { + Nan::HandleScope scope; + v8::Local context = Nan::GetCurrentContext(); + v8::Isolate *isolate = context->GetIsolate(); + return contexts[isolate]; + } + + 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()); + } + + 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); + } + + void Context::ShutdownThreadPool(std::unique_ptr cleanupHandle) { + threadPool.Shutdown(std::move(cleanupHandle)); + } +} diff --git a/generate/templates/manual/src/convenient_hunk.cc b/generate/templates/manual/src/convenient_hunk.cc index 184f015a1..755783e74 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() { @@ -92,22 +96,27 @@ 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(); + 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); 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); 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; @@ -121,6 +130,15 @@ void ConvenientHunk::LinesWorker::Execute() { } } +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(); Local result = Nan::New(size); @@ -136,6 +154,8 @@ void ConvenientHunk::LinesWorker::HandleOKCallback() { result }; callback->Call(2, argv, async_resource); + + delete baton; } NAN_METHOD(ConvenientHunk::OldStart) { @@ -181,5 +201,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..99096a390 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" @@ -32,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; @@ -55,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) { @@ -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() { @@ -208,26 +212,30 @@ 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(); + 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); 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; - 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; @@ -254,6 +262,20 @@ void ConvenientPatch::HunksWorker::Execute() { } } +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(); Local result = Nan::New(size); @@ -269,6 +291,8 @@ void ConvenientPatch::HunksWorker::HandleOKCallback() { result }; callback->Call(2, argv, async_resource); + + delete baton; } NAN_METHOD(ConvenientPatch::LineStats) { @@ -396,5 +420,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..734f20c1a 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) { @@ -52,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()); @@ -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,18 @@ 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); int result = git_filter_register(baton->filter_name, baton->filter, baton->filter_priority); baton->error_code = result; @@ -90,6 +95,20 @@ void GitFilterRegistry::RegisterWorker::Execute() { } } +void GitFilterRegistry::RegisterWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + free(baton->filter_name); + + delete baton; +} + void GitFilterRegistry::RegisterWorker::HandleOKCallback() { if (baton->error_code == GIT_OK) { v8::Local result = Nan::New(baton->error_code); @@ -128,8 +147,10 @@ void GitFilterRegistry::RegisterWorker::HandleOKCallback() { else { callback->Call(0, NULL, async_resource); } + + free(baton->filter_name); + delete baton; - return; } NAN_METHOD(GitFilterRegistry::GitFilterUnregister) { @@ -143,7 +164,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); @@ -158,15 +179,19 @@ 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); int result = git_filter_unregister(baton->filter_name); baton->error_code = result; @@ -176,8 +201,25 @@ void GitFilterRegistry::UnregisterWorker::Execute() { } } +void GitFilterRegistry::UnregisterWorker::HandleErrorCallback() { + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + free(baton->filter_name); + + delete baton; +} + 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(), @@ -214,6 +256,8 @@ void GitFilterRegistry::UnregisterWorker::HandleOKCallback() { else { callback->Call(0, NULL, async_resource); } + + free(baton->filter_name); + delete baton; - return; } diff --git a/generate/templates/manual/src/lock_master.cc b/generate/templates/manual/src/lock_master.cc index 30679b534..8ad33378f 100644 --- a/generate/templates/manual/src/lock_master.cc +++ b/generate/templates/manual/src/lock_master.cc @@ -1,246 +1,239 @@ #include #include -#include #include #include #include #include +#include +#include +#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 { - uv_mutex_t *mutex; - unsigned useCount; - - ObjectInfo(uv_mutex_t *mutex, unsigned useCount) - : mutex(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 uv_mutex_t mapMutex; - - // A libuv key used to store the current thread-specific LockMasterImpl instance - static uv_key_t currentLockMasterKey; - - // Cleans up any mutexes that are not currently used - static NAN_GC_CALLBACK(CleanupMutexes); + LockMasterImpl() { + Register(); + } -public: - static void Initialize(); + ~LockMasterImpl() { + Unregister(); + Unlock(true); + } - // INSTANCE variables / methods + void ObjectToLock(const void *objectToLock) { + objectsToLock.insert(objectToLock); + } -private: - // The set of objects this LockMaster is responsible for locking - std::set objectsToLock; + void Lock(bool acquireMutexes); + void Unlock(bool releaseMutexes); + }; - // Mutexes locked by this LockMaster on construction and unlocked on destruction - std::vector GetMutexes(int useCountDelta); - void Register(); - void Unregister(); + std::map LockMasterImpl::mutexes; + std::mutex LockMasterImpl::mapMutex; + thread_local LockMasterImpl* LockMasterImpl::currentLockMaster = nullptr; -public: - static LockMasterImpl *CurrentLockMasterImpl() { - return (LockMasterImpl *)uv_key_get(¤tLockMasterKey); + LockMaster::LockMaster(LockMaster &&other) { + impl = other.impl; + other.impl = nullptr; } - static LockMaster::Diagnostics GetDiagnostics(); - LockMasterImpl() { - Register(); - } + LockMaster &LockMaster::operator=(LockMaster &&other) { + if (&other == this) { + return *this; + } - ~LockMasterImpl() { - Unregister(); - Unlock(true); - } + impl = other.impl; + other.impl = nullptr; - void ObjectToLock(const void *objectToLock) { - objectsToLock.insert(objectToLock); + return *this; } - void Lock(bool acquireMutexes); - void Unlock(bool releaseMutexes); -}; - -std::map LockMasterImpl::mutexes; -uv_mutex_t LockMasterImpl::mapMutex; -uv_key_t LockMasterImpl::currentLockMasterKey; - -void LockMasterImpl::Initialize() { - uv_mutex_init(&mapMutex); - uv_key_create(¤tLockMasterKey); - Nan::AddGCEpilogueCallback(CleanupMutexes); -} + 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(); ) - { - 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 - if (!useCount) { - uv_mutex_destroy(mutex); - free(mutex); - auto to_erase = it; - it++; - mutexes.erase(to_erase); - } else { - it++; + 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++; + } } } - uv_mutex_unlock(&mapMutex); -} + void LockMaster::InitializeContext() { + LockMasterImpl::InitializeContext(); + } -void LockMaster::Initialize() { - LockMasterImpl::Initialize(); -} + 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; - - uv_mutex_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((uv_mutex_t *)malloc(sizeof(uv_mutex_t)), 0U) - ) - ).first; - uv_mutex_init(mutexIt->second.mutex); + objectMutexes.push_back(mutexIt->second.mutex); + mutexIt->second.useCount += useCountDelta; } - - objectMutexes.push_back(mutexIt->second.mutex); - mutexIt->second.useCount += useCountDelta; } - } - uv_mutex_unlock(&mapMutex); + return objectMutexes; + } - return objectMutexes; -} + void LockMasterImpl::Register() { + currentLockMaster = this; + } -void LockMasterImpl::Register() { - uv_key_set(¤tLockMasterKey, this); -} + void LockMasterImpl::Unregister() { + currentLockMaster = nullptr; + } -void LockMasterImpl::Unregister() { - uv_key_set(¤tLockMasterKey, NULL); -} + 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::Lock(bool acquireMutexes) { - std::vector objectMutexes = GetMutexes(acquireMutexes * 1); - - auto alreadyLocked = objectMutexes.end(); - - // 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, - // 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) { - // we have failed to lock a mutex... unlock everything we have locked - std::for_each(objectMutexes.begin(), it, uv_mutex_unlock); - if (alreadyLocked > it && alreadyLocked != objectMutexes.end()) { - uv_mutex_unlock(*alreadyLocked); + // 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; } - // now do a blocking lock on what we couldn't lock - uv_mutex_lock(*it); - // 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()); -} - -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); + } while (it != objectMutexes.end()); + } - std::for_each(objectMutexes.begin(), objectMutexes.end(), uv_mutex_unlock); + 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); - GetMutexes(releaseMutexes * -1); -} + std::for_each(objectMutexes.begin(), objectMutexes.end(), [](std::shared_ptr mutex) { + mutex->unlock(); + }); -LockMaster::Diagnostics LockMasterImpl::GetDiagnostics() { - LockMaster::Diagnostics diagnostics; - uv_mutex_lock(&LockMasterImpl::mapMutex); - diagnostics.storedMutexesCount = mutexes.size(); - uv_mutex_unlock(&LockMasterImpl::mapMutex); - return diagnostics; -} + 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::Diagnostics LockMaster::GetDiagnostics() { - return LockMasterImpl::GetDiagnostics(); -} + // 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); } - -LockMaster::Status LockMaster::status = LockMaster::Disabled; diff --git a/generate/templates/manual/src/nodegit_wrapper.cc b/generate/templates/manual/src/nodegit_wrapper.cc index 69aed4f94..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); @@ -79,9 +81,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 +104,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/thread_pool.cc b/generate/templates/manual/src/thread_pool.cc index ef7a4528d..6c8e6776e 100644 --- a/generate/templates/manual/src/thread_pool.cc +++ b/generate/templates/manual/src/thread_pool.cc @@ -1,106 +1,729 @@ #include +#include "../include/context.h" #include "../include/thread_pool.h" -ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop) { - uv_mutex_init(&workMutex); - uv_sem_init(&workSemaphore, 0); +#include +#include +#include +#include +#include - uv_async_init(loop, &loopAsync, RunLoopCallbacks); - loopAsync.data = this; - uv_unref((uv_handle_t *)&loopAsync); - uv_mutex_init(&loopMutex); +extern "C" { + #include +} + +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, + nodegit::Context *context + ); + + 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 const nodegit::Context *GetCurrentContext(); + + static void PostCallbackEvent(ThreadPool::OnPostCallbackFn onPostCallback); - workInProgressCount = 0; + // 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(); - for(int i=0; i task = takeNextTask(); + if (task->type == Task::Type::SHUTDOWN) { + return; + } + + WorkTask *workTask = static_cast(task.get()); + + currentAsyncResource = workTask->asyncResource; + workTask->callback(); + currentAsyncResource = nullptr; + + postCompletedEventToOrchestrator(); + } } -} -void ThreadPool::QueueWork(Callback workCallback, Callback completionCallback, void *data) { - uv_mutex_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++; - uv_mutex_unlock(&workMutex); - uv_sem_post(&workSemaphore); -} + void Executor::WaitForThreadClose() { + thread.join(); + } -void ThreadPool::QueueLoopCallback(Callback callback, void *data, bool isWork) { - // push the callback into the queue - uv_mutex_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); - } - uv_mutex_unlock(&loopMutex); -} + Nan::AsyncResource *Executor::GetCurrentAsyncResource() { + if (executor) { + return executor->currentAsyncResource; + } -void ThreadPool::ExecuteReverseCallback(Callback reverseCallback, void *data) { - QueueLoopCallback(reverseCallback, data, false); -} + // NOTE this should always be set when a libgit2 callback is running, + // so this case should not happen. + return nullptr; + } -void ThreadPool::RunEventQueue(void *threadPool) { - static_cast(threadPool)->RunEventQueue(); -} + const nodegit::Context *Executor::GetCurrentContext() { + if (executor) { + return executor->currentContext; + } -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); + // NOTE this should always be set when a libgit2 callback is running, + // so this case should not happen. + return nullptr; + } - // perform the queued work - (*work.workCallback)(work.data); + void Executor::PostCallbackEvent(ThreadPool::OnPostCallbackFn onPostCallback) { + if (executor) { + executor->postCallbackEventToOrchestrator(onPostCallback); + } + } - // schedule the completion callback on the loop - QueueLoopCallback(work.completionCallback, work.data, true); + void *Executor::RetrieveTLSForLibgit2ChildThread() { + return Executor::executor; } -} -void ThreadPool::RunLoopCallbacks(uv_async_t* handle) { - static_cast(handle->data)->RunLoopCallbacks(); -} + void Executor::SetTLSForLibgit2ChildThread(void *vexecutor) { + Executor::executor = static_cast(vexecutor); + } + + void Executor::TeardownTLSOnLibgit2ChildThread() { + Executor::executor = nullptr; + } + + 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, + nodegit::Context *context + ); + + 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, + nodegit::Context *context + ); + + void WaitForThreadClose(); + }; + + Orchestrator::OrchestratorImpl::OrchestratorImpl( + QueueCallbackOnJSThreadFn queueCallbackOnJSThread, + TakeNextJobFn takeNextJob, + nodegit::Context *context + ) + : 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), + context + ) + {} + + void Orchestrator::OrchestratorImpl::RunJobLoop() { + for ( ; ; ) { + auto job = takeNextJob(); + 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, ThreadPool::Callback cancelCallback) { + queueCallbackOnJSThread(callback, cancelCallback, 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(); + }, + [worker]() { + worker->Cancel(); + worker->WorkComplete(); + worker->Destroy(); + }, + true + ); + } + } + } + } + + // 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)); + 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(); + } + + Orchestrator::Orchestrator( + QueueCallbackOnJSThreadFn queueCallbackOnJSThread, + TakeNextJobFn takeNextJob, + nodegit::Context *context + ) + : impl(new OrchestratorImpl(queueCallbackOnJSThread, takeNextJob, context)) + {} + + void Orchestrator::WaitForThreadClose() { + impl->WaitForThreadClose(); + } + + class ThreadPoolImpl { + public: + ThreadPoolImpl(int numberOfThreads, uv_loop_t *loop, nodegit::Context *context); + + void QueueWorker(nodegit::AsyncWorker *worker); + + std::shared_ptr TakeNextJob(); + + void QueueCallbackOnJSThread(ThreadPool::Callback callback, ThreadPool::Callback cancelCallback, bool isWork); + + static void RunJSThreadCallbacksFromOrchestrator(uv_async_t *handle); + + void RunJSThreadCallbacksFromOrchestrator(); + + static void RunLoopCallbacks(uv_async_t *handle); + + void Shutdown(std::unique_ptr cleanupHandle); + + struct AsyncCallbackData { + AsyncCallbackData(ThreadPoolImpl *pool) + : pool(pool) + {} + + std::unique_ptr cleanupHandle; + ThreadPoolImpl *pool; + }; + + private: + bool isMarkedForDeletion; + nodegit::Context *currentContext; + + struct JSThreadCallback { + JSThreadCallback(ThreadPool::Callback callback, ThreadPool::Callback cancelCallback, bool isWork) + : isWork(isWork), callback(callback), cancelCallback(cancelCallback) + {} + + JSThreadCallback() + : isWork(false), callback(nullptr), cancelCallback(nullptr) + {} + + void performCallback() { + callback(); + } + + void cancel() { + cancelCallback(); + } + + bool isWork; + + private: + ThreadPool::Callback callback; + ThreadPool::Callback cancelCallback; + }; + + void 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, nodegit::Context *context) + : isMarkedForDeletion(false), + currentContext(context), + orchestratorJobMutex(new std::mutex), + jsThreadCallbackMutex(new std::mutex) + { + uv_async_init(loop, &jsThreadCallbackAsync, RunLoopCallbacks); + jsThreadCallbackAsync.data = new AsyncCallbackData(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, _3), + std::bind(&ThreadPoolImpl::TakeNextJob, this), + context + ); + } + } + + 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); + orchestratorJobQueue.emplace(new Orchestrator::AsyncWorkJob(worker)); + 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, 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, 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) { + uv_async_send(&jsThreadCallbackAsync); + } + } + + void ThreadPoolImpl::RunLoopCallbacks(uv_async_t* handle) { + auto asyncCallbackData = static_cast(handle->data); + if (asyncCallbackData->pool) { + asyncCallbackData->pool->RunLoopCallbacks(); + } + } + + // NOTE this should theoretically never be triggered during a cleanup operation + 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(); + jsThreadCallbackQueue.pop(); + + lock.unlock(); + jsThreadCallback.performCallback(); + lock.lock(); + + 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); + } + } + } + + void ThreadPoolImpl::Shutdown(std::unique_ptr cleanupHandle) { + 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(); + } + + AsyncCallbackData *asyncCallbackData = static_cast(jsThreadCallbackAsync.data); + asyncCallbackData->cleanupHandle.swap(cleanupHandle); + asyncCallbackData->pool = nullptr; + + uv_close(reinterpret_cast(&jsThreadCallbackAsync), [](uv_handle_t *handle) { + auto asyncCallbackData = static_cast(handle->data); + delete asyncCallbackData; + }); + } + + ThreadPool::ThreadPool(int numberOfThreads, uv_loop_t *loop, nodegit::Context *context) + : impl(new ThreadPoolImpl(numberOfThreads, loop, context)) + {} + + 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(); + } + + const nodegit::Context *ThreadPool::GetCurrentContext() { + return Executor::GetCurrentContext(); + } + + void ThreadPool::Shutdown(std::unique_ptr cleanupHandle) { + impl->Shutdown(std::move(cleanupHandle)); + } -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); - - // 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); - } - 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); - workInProgressCount --; - if(!workInProgressCount) { - uv_unref((uv_handle_t *)&loopAsync); - } - uv_mutex_unlock(&workMutex); + 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..6b64ad443 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; @@ -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%} } @@ -74,25 +76,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 +129,76 @@ void {{ cppClassName }}::{{ cppFunctionName }}Worker::Execute() { baton->result = result; {%endif%} +} + +void {{ 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()); + v8::Local argv[1] = { + err + }; + callback->Call(1, argv, async_resource); } + + if (baton->error) { + if (baton->error->message) { + free((void *)baton->error->message); + } + + free((void *)baton->error); + } + + {%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%} + {%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 %} + {%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%} + + delete baton; } void {{ cppClassName }}::{{ cppFunctionName }}Worker::HandleOKCallback() { @@ -257,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%} } @@ -264,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%} diff --git a/generate/templates/partials/callback_helpers.cc b/generate/templates/partials/callback_helpers.cc index c3810371c..36e20891c 100644 --- a/generate/templates/partials/callback_helpers.cc +++ b/generate/templates/partials/callback_helpers.cc @@ -12,7 +12,13 @@ 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) { + {{ 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) { @@ -49,8 +55,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 +94,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..26f52ef4d 100644 --- a/generate/templates/partials/field_accessors.cc +++ b/generate/templates/partials/field_accessors.cc @@ -138,32 +138,44 @@ {{ 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); + baton->ExecuteAsync({{ field.name }}_async, {{ field.name }}_cancelAsync); delete baton; } else { - baton->ExecuteAsync({{ field.name }}_async, deleteBaton); + baton->ExecuteAsync({{ field.name }}_async, {{ field.name }}_cancelAsync, nodegit::deleteBaton); } return; {% 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()) { - 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, deleteBaton); + baton->ExecuteAsync({{ field.name }}_async, {{ field.name }}_cancelAsync, nodegit::deleteBaton); } return result; {% endif %} } + 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) { Nan::HandleScope scope; @@ -205,8 +217,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 +262,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 1b23ed2fd..d47e32f31 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", @@ -60,7 +62,7 @@ "src/filter_registry.cc", "src/git_buf_converter.cc", "src/str_array_converter.cc", - "src/thread_pool.cc", + "src/context.cc", {% each %} {% if type != "enum" %} "src/{{ name }}.cc", 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..e828b7f89 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 %} @@ -70,15 +73,17 @@ 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, 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 +109,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 +133,18 @@ 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 HandleErrorCallback(); 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 6a3fa8ef4..45ce409fb 100644 --- a/generate/templates/templates/nodegit.cc +++ b/generate/templates/templates/nodegit.cc @@ -5,12 +5,13 @@ #include #include #include - #include +#include #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" @@ -23,70 +24,22 @@ #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; - 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); - } +using namespace v8; - void SetPrivate(v8::Local object, - v8::Local key, - v8::Local value) { - object->SetHiddenValue(key, value); - } -#endif - -void LockMasterEnable(const FunctionCallbackInfo& info) { - LockMaster::Enable(); +Local GetPrivate(Local object, Local key) { + Local value; + Nan::Maybe result = Nan::HasPrivate(object, key); + if (!(result.IsJust() && result.FromJust())) + return Local(); + if (Nan::GetPrivate(object, key).ToLocal(&value)) + return value; + return Local(); } -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); +void SetPrivate(Local object, Local key, Local value) { + if (value.IsEmpty()) + return; + Nan::SetPrivate(object, key, value); } static uv_mutex_t *opensslMutexes; @@ -114,42 +67,45 @@ void OpenSSL_ThreadSetup() { CRYPTO_THREADID_set_callback(OpenSSL_IDCallback); } -ThreadPool libgit2ThreadPool(10, uv_default_loop()); - -extern "C" void init(v8::Local target) { - // Initialize thread safety in openssl and libssh2 - OpenSSL_ThreadSetup(); - init_ssh2(); - // Initialize libgit2. - git_libgit2_init(); +static std::once_flag libraryInitializedFlag; +static std::mutex libraryInitializationMutex; + +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. + // 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(); + + // 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); - - 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); + ConvenientHunk::InitializeComponent(target, nodegitContext); + ConvenientPatch::InitializeComponent(target, nodegitContext); + GitFilterRegistry::InitializeComponent(target, nodegitContext); - LockMaster::Initialize(); + nodegit::LockMaster::InitializeContext(); } -NODE_MODULE(nodegit, init) +NAN_MODULE_WORKER_ENABLED(nodegit, init) diff --git a/generate/templates/templates/nodegit.js b/generate/templates/templates/nodegit.js index c22d7d999..bb9739ac9 100644 --- a/generate/templates/templates/nodegit.js +++ b/generate/templates/templates/nodegit.js @@ -1,5 +1,11 @@ var _ = require("lodash"); var util = require("util"); +var worker; + +try { + worker = require("worker_threads"); +} catch (e) {} + var rawApi; // Attempt to load the production release first, if it fails fall back to the 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..6ef905c92 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 %} @@ -51,26 +53,29 @@ 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, 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 %} 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. 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..c69caf907 100644 --- a/package.json +++ b/package.json @@ -35,14 +35,14 @@ "lib": "./lib" }, "engines": { - "node": ">= 6" + "node": ">= 12.19.0 < 13 || >= 14.10.0" }, "dependencies": { "fs-extra": "^7.0.0", "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", 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/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; 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); - } - }); -}); diff --git a/test/tests/worker.js b/test/tests/worker.js new file mode 100644 index 000000000..e141b58aa --- /dev/null +++ b/test/tests/worker.js @@ -0,0 +1,88 @@ +const path = require("path"); +const assert = require("assert"); +const fse = require("fs-extra"); +const local = path.join.bind(path, __dirname); + +let Worker; + +try { + Worker = require("worker_threads").Worker; +} catch (e) {} + +if (Worker) { + 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/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"));