Skip to content

Commit 2e5451f

Browse files
committed
Add additional GC behavior that reflects new descriptor format
- Implements isSingleton behavior with reference counting - Implements ownerFn, which ties an arbitrary pointer to a singleton type pointer - always calls free, even if object is "owned", the ownership link is about keeping internal pointers alive, but it seems that most calls to free are reference counted in libgit2, but the repository has a different relationship than all other types of ref counted frees.
1 parent 0135189 commit 2e5451f

10 files changed

Lines changed: 106 additions & 14 deletions

File tree

generate/templates/filters/returns_info.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ module.exports = function(fn, argReturnsOnly, isAsync) {
1212
// any sort of string to argument index
1313
// in the template.
1414
var nameToArgIndex = {};
15+
var thisArgName = '';
1516
args.forEach(function (arg, index) {
1617
nameToArgIndex[arg.name] = index;
18+
if (arg.isSelf) {
19+
thisArgName = arg.name;
20+
}
1721
});
1822

1923
args.forEach(function (arg) {
@@ -34,9 +38,13 @@ module.exports = function(fn, argReturnsOnly, isAsync) {
3438
return_info.returnNameOrName = return_info.returnName || return_info.name;
3539
return_info.jsOrCppClassName = return_info.jsClassName || return_info.cppClassName;
3640
return_info.isOutParam = true;
37-
return_info.hasOwner = !!(return_info.ownedBy || return_info.ownedByThis);
41+
return_info.hasOwner = !!(return_info.ownedBy || return_info.ownedByThis || return_info.ownerFn);
3842
return_info.ownedByIndex = -1;
3943

44+
if (return_info.ownedByThis) {
45+
return_info.ownedBy = thisArgName;
46+
}
47+
4048
// Here we convert ownedBy, which is the name of the parameter
4149
// that owns this result to the argument index.
4250
// sync functions will need to know this.
@@ -57,7 +65,10 @@ module.exports = function(fn, argReturnsOnly, isAsync) {
5765
return_info.__proto__ = fn.return;
5866

5967
return_info.isAsync = isAsync;
60-
return_info.hasOwner = !!return_info.ownedByThis;
68+
return_info.hasOwner = !!(return_info.ownedByThis || return_info.ownerFn);
69+
if (return_info.ownedByThis) {
70+
return_info.ownedBy = thisArgName;
71+
}
6172
return_info.ownedByIndex = -1;
6273
return_info.parsedName = return_info.name && isAsync ? "baton->" + return_info.name : "result";
6374
return_info.isCppClassIntType = ~['Uint32', 'Int32'].indexOf(return_info.cppClassName);

generate/templates/manual/include/nodegit_wrapper.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define NODEGIT_WRAPPER_H
33

44
#include <nan.h>
5+
#include <unordered_map>
56

67
// the Traits template parameter supplies:
78
// typename cppClass - the C++ type of the NodeGit wrapper (e.g. GitRepository)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#ifndef REFERENCE_COUNTER_H
2+
#define REFERENCE_COUNTER_H
3+
4+
#include <unordered_map>
5+
6+
#include "lock_master.h"
7+
8+
// There are certain instances in libgit2 which can be retrieved from multiple sources
9+
// We need to make sure that we're counting how many times we've seen that pointer
10+
// so that when we are performing free behavior, we don't free it until it is no longer
11+
// referenced. The main example of this behavior is the repository instance, where
12+
// after git_repository_open open (first instance) we can git_commit_lookup, followed by
13+
// git_commit_owner (second instance).
14+
//
15+
// I was hoping that we could construct a Persistent handle, but that would interfere with
16+
// GC. We want it to attmept to GC, and if this handle exists, the final repo will not
17+
// free itself :(.
18+
//
19+
// Make sure to utilize LockMaster when incrementing or decrementing a reference count.
20+
class ReferenceCounter {
21+
public:
22+
static void incrementCountForPointer(void *ptr);
23+
24+
static unsigned long decrementCountForPointer(void *ptr);
25+
26+
private:
27+
static std::unordered_map<void *, unsigned long> referenceCountByPointer;
28+
};
29+
30+
#endif

generate/templates/manual/src/nodegit_wrapper.cc

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
template<typename Traits>
22
NodeGitWrapper<Traits>::NodeGitWrapper(typename Traits::cType *raw, bool selfFreeing, v8::Local<v8::Object> owner) {
3-
if (!owner.IsEmpty()) {
4-
// if we have an owner, there are two options - either we duplicate the raw object
5-
// (so we own the duplicate, and can self-free it)
6-
// or we keep a handle on the owner so it doesn't get garbage collected
7-
// while this wrapper is accessible
8-
if(Traits::isDuplicable) {
3+
if (Traits::isSingleton) {
4+
ReferenceCounter::incrementCountForPointer((void *)raw);
5+
this->raw = raw;
6+
} else if (!owner.IsEmpty()) {
7+
// if we have an owner, it could mean 2 things:
8+
// 1. We are borrowed memory from another struct and should not be freed. We will keep a handle to the owner
9+
// so that the owner isn't gc'd while we are using its memory.
10+
// 2. We are borrowed memory from another struct and can be duplicated, so we should duplicate
11+
// and become selfFreeing.
12+
// 3. We are cached memory, potentially on the repo or config.
13+
// Even though we have a handle in another objects cache, we are expected to call free,
14+
// otherwise we are leaking memory. Cached objects are reference counted in libgit2, but will be leaked
15+
// even if the cache is cleared if we haven't freed them. We will keep a handle on the owner, even though it
16+
// is probably safe as we're reference counted. This should at worst just ensure that the cache owner is the
17+
// last thing to be freed, and that is more safety than anything else.
18+
if (Traits::isDuplicable) {
919
Traits::duplicate(&this->raw, raw);
1020
selfFreeing = true;
1121
} else {
1222
this->owner.Reset(owner);
1323
this->raw = raw;
14-
selfFreeing = false;
1524
}
1625
} else {
1726
this->raw = raw;
@@ -34,7 +43,7 @@ NodeGitWrapper<Traits>::NodeGitWrapper(const char *error) {
3443

3544
template<typename Traits>
3645
NodeGitWrapper<Traits>::~NodeGitWrapper() {
37-
if(Traits::isFreeable && selfFreeing) {
46+
if (Traits::isFreeable && selfFreeing) {
3847
Traits::free(raw);
3948
SelfFreeingInstanceCount--;
4049
raw = NULL;
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "../include/reference_counter.h"
2+
3+
void ReferenceCounter::incrementCountForPointer(void *ptr) {
4+
LockMaster(true, &referenceCountByPointer);
5+
if (referenceCountByPointer.find(ptr) == referenceCountByPointer.end()) {
6+
referenceCountByPointer[ptr] = 1;
7+
} else {
8+
referenceCountByPointer[ptr] = referenceCountByPointer[ptr] + 1;
9+
}
10+
}
11+
12+
unsigned long ReferenceCounter::decrementCountForPointer(void *ptr) {
13+
LockMaster(true, &referenceCountByPointer);
14+
unsigned long referenceCount = referenceCountByPointer[ptr];
15+
if (referenceCount == 1) {
16+
referenceCountByPointer.erase(ptr);
17+
return 0;
18+
} else {
19+
referenceCountByPointer[ptr] = referenceCount - 1;
20+
return referenceCountByPointer[ptr];
21+
}
22+
}
23+
24+
std::unordered_map<void *, unsigned long> ReferenceCounter::referenceCountByPointer;

generate/templates/partials/convert_to_v8.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,15 @@
7070
{{ selfFreeing|toBool }}
7171
{% if hasOwner %}
7272
,
73-
{% if ownedByThis %}
74-
info.This()
73+
{% if ownerFn | toBool %}
74+
{{= ownerFn.singletonCppClassName =}}::New(
75+
{{= ownerFn.name =}}({{= parsedName =}}),
76+
true
77+
)->ToObject()
7578
{% elsif isAsync %}
76-
this->GetFromPersistent("{{= ownedBy =}}")
79+
this->GetFromPersistent("{{= ownedBy =}}")->ToObject()
80+
{% elsif ownedByThis %}
81+
info.This()
7782
{% else %}
7883
info[{{= ownedByIndex =}}]->ToObject()
7984
{% endif %}

generate/templates/partials/traits.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,17 @@ struct {{ cppClassName }}Traits {
1717
{% endif %}
1818
}
1919

20+
static const bool isSingleton = {{ isSingleton | toBool }};
2021
static const bool isFreeable = {{ freeFunctionName | toBool}};
2122
static void free({{ cType }} *raw) {
2223
{% if freeFunctionName %}
23-
::{{ freeFunctionName }}(raw); // :: to avoid calling this free recursively
24+
unsigned long referenceCount = 0;
25+
{% if isSingleton %}
26+
referenceCount = ReferenceCounter::decrementCountForPointer((void *)raw);
27+
{% endif %}
28+
if (referenceCount == 0) {
29+
::{{ freeFunctionName }}(raw); // :: to avoid calling this free recursively
30+
}
2431
{% else %}
2532
Nan::ThrowError("free called on {{ cppClassName }} which cannot be freed");
2633
{% endif %}

generate/templates/templates/binding.gyp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"sources": [
7676
"src/async_baton.cc",
7777
"src/lock_master.cc",
78+
"src/reference_counter.cc",
7879
"src/nodegit.cc",
7980
"src/init_ssh2.cc",
8081
"src/promise_completion.cc",

generate/templates/templates/class_header.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
#include <string>
55
#include <queue>
66
#include <utility>
7+
#include <unordered_map>
78

89
#include "async_baton.h"
910
#include "nodegit_wrapper.h"
1011
#include "promise_completion.h"
12+
#include "reference_counter.h"
1113

1214
extern "C" {
1315
#include <git2.h>

generate/templates/templates/struct_header.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
#include <string>
55
#include <queue>
66
#include <utility>
7+
#include <unordered_map>
78

89
#include "async_baton.h"
910
#include "callback_wrapper.h"
11+
#include "reference_counter.h"
1012
#include "nodegit_wrapper.h"
1113

1214
extern "C" {

0 commit comments

Comments
 (0)