Skip to content

Commit 04df7db

Browse files
addaleaxtargos
authored andcommitted
worker: keep allocators for transferred SAB instances alive longer
Keep the `ArrayBuffer::Allocator` behind a `SharedArrayBuffer` instance alive for at least as long as the receiving Isolate lives, if the `SharedArrayBuffer` instance isn’t already destroyed through GC. This is to work around the fact that V8 7.9 started refactoring how backing stores for `SharedArrayBuffer` instances work, changing the timing of the call that releases the backing store to be during Isolate disposal. The flag added to the test is optional but helps verify that the backing store is actually free’d at the end of the test and does not leak memory. Fixes: nodejs/node-v8#115 PR-URL: #29637 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
1 parent 91e4cc7 commit 04df7db

5 files changed

+51
-0
lines changed

src/env.cc

+15
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,21 @@ char* Environment::Reallocate(char* data, size_t old_size, size_t size) {
10361036
return new_data;
10371037
}
10381038

1039+
void Environment::AddArrayBufferAllocatorToKeepAliveUntilIsolateDispose(
1040+
std::shared_ptr<v8::ArrayBuffer::Allocator> allocator) {
1041+
if (keep_alive_allocators_ == nullptr) {
1042+
MultiIsolatePlatform* platform = isolate_data()->platform();
1043+
CHECK_NOT_NULL(platform);
1044+
1045+
keep_alive_allocators_ = new ArrayBufferAllocatorList();
1046+
platform->AddIsolateFinishedCallback(isolate(), [](void* data) {
1047+
delete static_cast<ArrayBufferAllocatorList*>(data);
1048+
}, static_cast<void*>(keep_alive_allocators_));
1049+
}
1050+
1051+
keep_alive_allocators_->insert(allocator);
1052+
}
1053+
10391054
void AsyncRequest::Install(Environment* env, void* data, uv_async_cb target) {
10401055
CHECK_NULL(async_);
10411056
env_ = env;

src/env.h

+8
Original file line numberDiff line numberDiff line change
@@ -1252,6 +1252,10 @@ class Environment : public MemoryRetainer {
12521252

12531253
#endif // HAVE_INSPECTOR
12541254

1255+
// Only available if a MultiIsolatePlatform is in use.
1256+
void AddArrayBufferAllocatorToKeepAliveUntilIsolateDispose(
1257+
std::shared_ptr<v8::ArrayBuffer::Allocator>);
1258+
12551259
private:
12561260
template <typename Fn>
12571261
inline void CreateImmediate(Fn&& cb,
@@ -1426,6 +1430,10 @@ class Environment : public MemoryRetainer {
14261430
// Used by embedders to shutdown running Node instance.
14271431
AsyncRequest thread_stopper_;
14281432

1433+
typedef std::unordered_set<std::shared_ptr<v8::ArrayBuffer::Allocator>>
1434+
ArrayBufferAllocatorList;
1435+
ArrayBufferAllocatorList* keep_alive_allocators_ = nullptr;
1436+
14291437
template <typename T>
14301438
void ForEachBaseObject(T&& iterator);
14311439

src/sharedarraybuffer_metadata.cc

+24
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,30 @@ class SABLifetimePartner : public BaseObject {
5050
: BaseObject(env, obj),
5151
reference(std::move(r)) {
5252
MakeWeak();
53+
env->AddCleanupHook(CleanupHook, static_cast<void*>(this));
54+
}
55+
56+
~SABLifetimePartner() {
57+
env()->RemoveCleanupHook(CleanupHook, static_cast<void*>(this));
58+
}
59+
60+
static void CleanupHook(void* data) {
61+
// There is another cleanup hook attached to this object because it is a
62+
// BaseObject. Cleanup hooks are triggered in reverse order of addition,
63+
// so if this object is destroyed through GC, the destructor removes all
64+
// hooks associated with this object, meaning that this cleanup hook
65+
// only runs at the end of the Environment’s lifetime.
66+
// In that case, V8 still knows about the SharedArrayBuffer and tries to
67+
// free it when the last Isolate with access to it is disposed; for that,
68+
// the ArrayBuffer::Allocator needs to be kept alive longer than this
69+
// object and longer than the Environment instance.
70+
//
71+
// This is a workaround for https://github.com/nodejs/node-v8/issues/115
72+
// (introduced in V8 7.9) and we should be able to remove it once V8
73+
// ArrayBuffer::Allocator refactoring/removal is complete.
74+
SABLifetimePartner* self = static_cast<SABLifetimePartner*>(data);
75+
self->env()->AddArrayBufferAllocatorToKeepAliveUntilIsolateDispose(
76+
self->reference->allocator());
5377
}
5478

5579
SET_NO_MEMORY_INFO()

src/sharedarraybuffer_metadata.h

+3
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ class SharedArrayBufferMetadata
3737
// count is increased by 1.
3838
v8::MaybeLocal<v8::SharedArrayBuffer> GetSharedArrayBuffer(
3939
Environment* env, v8::Local<v8::Context> context);
40+
std::shared_ptr<v8::ArrayBuffer::Allocator> allocator() const {
41+
return allocator_;
42+
}
4043

4144
SharedArrayBufferMetadata(SharedArrayBufferMetadata&& other) = delete;
4245
SharedArrayBufferMetadata& operator=(

test/parallel/test-worker-sharedarraybuffer-from-worker-thread.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Flags: --debug-arraybuffer-allocations
12
'use strict';
23
const common = require('../common');
34
const assert = require('assert');

0 commit comments

Comments
 (0)