Skip to content

Commit acd9bed

Browse files
committed
Add self-freeing diagnostic instance counting and leak test
1 parent f708658 commit acd9bed

File tree

3 files changed

+77
-0
lines changed

3 files changed

+77
-0
lines changed

generate/templates/templates/class_content.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,21 @@ using namespace node;
2727
{{ cppClassName }}::{{ cppClassName }}({{ cType }} *raw, bool selfFreeing) {
2828
this->raw = raw;
2929
this->selfFreeing = selfFreeing;
30+
31+
if (selfFreeing) {
32+
SelfFreeingInstanceCount++;
33+
} else {
34+
NonSelfFreeingConstructedCount++;
35+
}
36+
3037
}
3138

3239
{{ cppClassName }}::~{{ cppClassName }}() {
3340
{% if freeFunctionName %}
3441
if (this->selfFreeing) {
3542
{{ freeFunctionName }}(this->raw);
43+
SelfFreeingInstanceCount--;
44+
3645
this->raw = NULL;
3746
}
3847
{% endif %}
@@ -77,6 +86,9 @@ using namespace node;
7786
{% endif %}
7887
{% endeach %}
7988

89+
Nan::SetMethod(tpl, "getSelfFreeingInstanceCount", GetSelfFreeingInstanceCount);
90+
Nan::SetMethod(tpl, "getNonSelfFreeingConstructedCount", GetNonSelfFreeingConstructedCount);
91+
8092
Local<Function> _constructor_template = Nan::GetFunction(tpl).ToLocalChecked();
8193
constructor_template.Reset(_constructor_template);
8294
Nan::Set(target, Nan::New("{{ jsClassName }}").ToLocalChecked(), _constructor_template);
@@ -104,6 +116,14 @@ using namespace node;
104116
return scope.Escape(Nan::NewInstance(Nan::New({{ cppClassName }}::constructor_template), 2, argv).ToLocalChecked());
105117
}
106118

119+
NAN_METHOD({{ cppClassName }}::GetSelfFreeingInstanceCount) {
120+
info.GetReturnValue().Set(SelfFreeingInstanceCount);
121+
}
122+
123+
NAN_METHOD({{ cppClassName }}::GetNonSelfFreeingConstructedCount) {
124+
info.GetReturnValue().Set(NonSelfFreeingConstructedCount);
125+
}
126+
107127
{{ cType }} *{{ cppClassName }}::GetValue() {
108128
return this->raw;
109129
}
@@ -147,3 +167,6 @@ using namespace node;
147167
{% if not cTypeIsUndefined %}
148168
Nan::Persistent<Function> {{ cppClassName }}::constructor_template;
149169
{% endif %}
170+
171+
int {{ cppClassName }}::SelfFreeingInstanceCount;
172+
int {{ cppClassName }}::NonSelfFreeingConstructedCount;

generate/templates/templates/class_header.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ class {{ cppClassName }} : public Nan::ObjectWrap {
4040

4141
static Nan::Persistent<Function> constructor_template;
4242
static void InitializeComponent (Local<v8::Object> target);
43+
// diagnostic count of self-freeing object instances
44+
static int SelfFreeingInstanceCount;
45+
// diagnostic count of constructed non-self-freeing object instances
46+
static int NonSelfFreeingConstructedCount;
4347

4448
{%if cType%}
4549
{{ cType }} *GetValue();
@@ -96,6 +100,8 @@ class {{ cppClassName }} : public Nan::ObjectWrap {
96100
{% endeach %}
97101

98102
static NAN_METHOD(JSNewFunction);
103+
static NAN_METHOD(GetSelfFreeingInstanceCount);
104+
static NAN_METHOD(GetNonSelfFreeingConstructedCount);
99105

100106
{%each fields as field%}
101107
{%if not field.ignore%}

test/tests/commit.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,25 @@ var exec = promisify(function(command, opts, callback) {
99
return require("child_process").exec(command, opts, callback);
1010
});
1111

12+
// aggressively collects garbage until we fail to improve terminatingIterations
13+
// times.
14+
function garbageCollect() {
15+
var terminatingIterations = 3;
16+
var usedBeforeGC = Number.MAX_VALUE;
17+
var nondecreasingIterations = 0;
18+
for ( ; ; ) {
19+
global.gc();
20+
var usedAfterGC = process.memoryUsage().heapUsed;
21+
if (usedAfterGC >= usedBeforeGC) {
22+
nondecreasingIterations++;
23+
if (nondecreasingIterations >= terminatingIterations) {
24+
break;
25+
}
26+
}
27+
usedBeforeGC = usedAfterGC;
28+
}
29+
}
30+
1231
describe("Commit", function() {
1332
var NodeGit = require("../../");
1433
var Repository = NodeGit.Repository;
@@ -624,4 +643,33 @@ describe("Commit", function() {
624643
assert.equal(this.committer.email(), "mike@panmedia.co.nz");
625644
});
626645
});
646+
647+
it("does not leak", function() {
648+
var test = this;
649+
650+
garbageCollect();
651+
var Commit = NodeGit.Commit;
652+
var startSelfFreeingCount = Commit.getSelfFreeingInstanceCount();
653+
var startNonSelfFreeingCount = Commit.getNonSelfFreeingConstructedCount();
654+
655+
var resolve;
656+
var promise = new Promise(function(_resolve) { resolve = _resolve; });
657+
658+
NodeGit.Commit.lookup(test.repository, oid)
659+
.then(function() {
660+
// get out of this promise chain to help GC get rid of the commit
661+
setTimeout(resolve, 0);
662+
});
663+
664+
return promise
665+
.then(function() {
666+
garbageCollect();
667+
var endSelfFreeingCount = Commit.getSelfFreeingInstanceCount();
668+
var endNonSelfFreeingCount = Commit.getNonSelfFreeingConstructedCount();
669+
// any new self-freeing commits should have been freed
670+
assert.equal(startSelfFreeingCount, endSelfFreeingCount);
671+
// no new non-self-freeing commits should have been constructed
672+
assert.equal(startNonSelfFreeingCount, endNonSelfFreeingCount);
673+
});
674+
});
627675
});

0 commit comments

Comments
 (0)