summaryrefslogtreecommitdiff
path: root/src/sharedarraybuffer_metadata.cc
blob: fc3bcdf3d3b6b78319c0110ef7981bca1b067dd9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
#include "sharedarraybuffer_metadata.h"

#include "base_object-inl.h"
#include "memory_tracker-inl.h"
#include "node_errors.h"
#include "node_worker.h"
#include "util-inl.h"

#include <utility>

using v8::Context;
using v8::Function;
using v8::FunctionTemplate;
using v8::Local;
using v8::Maybe;
using v8::MaybeLocal;
using v8::Nothing;
using v8::Object;
using v8::SharedArrayBuffer;
using v8::Value;

namespace node {
namespace worker {

namespace {

// Yield a JS constructor for SABLifetimePartner objects in the form of a
// standard API object, that has a single field for containing the raw
// SABLifetimePartner* pointer.
Local<Function> GetSABLifetimePartnerConstructor(
    Environment* env, Local<Context> context) {
  Local<FunctionTemplate> templ;
  templ = env->sab_lifetimepartner_constructor_template();
  if (!templ.IsEmpty())
    return templ->GetFunction(context).ToLocalChecked();

  templ = BaseObject::MakeLazilyInitializedJSTemplate(env);
  templ->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(),
                                            "SABLifetimePartner"));
  env->set_sab_lifetimepartner_constructor_template(templ);

  return GetSABLifetimePartnerConstructor(env, context);
}

class SABLifetimePartner : public BaseObject {
 public:
  SABLifetimePartner(Environment* env,
                     Local<Object> obj,
                     SharedArrayBufferMetadataReference r)
    : BaseObject(env, obj),
      reference(std::move(r)) {
    MakeWeak();
    env->AddCleanupHook(CleanupHook, static_cast<void*>(this));
  }

  ~SABLifetimePartner() {
    env()->RemoveCleanupHook(CleanupHook, static_cast<void*>(this));
  }

  static void CleanupHook(void* data) {
    // There is another cleanup hook attached to this object because it is a
    // BaseObject. Cleanup hooks are triggered in reverse order of addition,
    // so if this object is destroyed through GC, the destructor removes all
    // hooks associated with this object, meaning that this cleanup hook
    // only runs at the end of the Environment’s lifetime.
    // In that case, V8 still knows about the SharedArrayBuffer and tries to
    // free it when the last Isolate with access to it is disposed; for that,
    // the ArrayBuffer::Allocator needs to be kept alive longer than this
    // object and longer than the Environment instance.
    //
    // This is a workaround for https://github.com/nodejs/node-v8/issues/115
    // (introduced in V8 7.9) and we should be able to remove it once V8
    // ArrayBuffer::Allocator refactoring/removal is complete.
    SABLifetimePartner* self = static_cast<SABLifetimePartner*>(data);
    self->env()->AddArrayBufferAllocatorToKeepAliveUntilIsolateDispose(
        self->reference->allocator());
  }

  SET_NO_MEMORY_INFO()
  SET_MEMORY_INFO_NAME(SABLifetimePartner)
  SET_SELF_SIZE(SABLifetimePartner)

  SharedArrayBufferMetadataReference reference;
};

}  // anonymous namespace

SharedArrayBufferMetadataReference
SharedArrayBufferMetadata::ForSharedArrayBuffer(
    Environment* env,
    Local<Context> context,
    Local<SharedArrayBuffer> source) {
  Local<Value> lifetime_partner;

  if (!source->GetPrivate(context,
                          env->sab_lifetimepartner_symbol())
                              .ToLocal(&lifetime_partner)) {
    return nullptr;
  }

  if (lifetime_partner->IsObject() &&
      env->sab_lifetimepartner_constructor_template()
         ->HasInstance(lifetime_partner)) {
    CHECK(source->IsExternal());
    SABLifetimePartner* partner =
        Unwrap<SABLifetimePartner>(lifetime_partner.As<Object>());
    CHECK_NOT_NULL(partner);
    return partner->reference;
  }

  if (source->IsExternal()) {
    // If this is an external SharedArrayBuffer but we do not see a lifetime
    // partner object, it was not us who externalized it. In that case, there
    // is no way to serialize it, because it's unclear how the memory
    // is actually owned.
    THROW_ERR_TRANSFERRING_EXTERNALIZED_SHAREDARRAYBUFFER(env);
    return nullptr;
  }

  // If the SharedArrayBuffer is coming from a Worker, we need to make sure
  // that the corresponding ArrayBuffer::Allocator lives at least as long as
  // the SharedArrayBuffer itself.
  worker::Worker* w = env->worker_context();
  std::shared_ptr<v8::ArrayBuffer::Allocator> allocator =
      w != nullptr ? w->array_buffer_allocator() : nullptr;

  SharedArrayBuffer::Contents contents = source->Externalize();
  SharedArrayBufferMetadataReference r(
      new SharedArrayBufferMetadata(contents, allocator));
  if (r->AssignToSharedArrayBuffer(env, context, source).IsNothing())
    return nullptr;
  return r;
}

Maybe<bool> SharedArrayBufferMetadata::AssignToSharedArrayBuffer(
    Environment* env, Local<Context> context,
    Local<SharedArrayBuffer> target) {
  CHECK(target->IsExternal());
  Local<Function> ctor = GetSABLifetimePartnerConstructor(env, context);
  Local<Object> obj;
  if (!ctor->NewInstance(context).ToLocal(&obj))
    return Nothing<bool>();

  new SABLifetimePartner(env, obj, shared_from_this());
  return target->SetPrivate(context,
                            env->sab_lifetimepartner_symbol(),
                            obj);
}

SharedArrayBufferMetadata::SharedArrayBufferMetadata(
    const SharedArrayBuffer::Contents& contents,
    std::shared_ptr<v8::ArrayBuffer::Allocator> allocator)
  : contents_(contents), allocator_(allocator) { }

SharedArrayBufferMetadata::~SharedArrayBufferMetadata() {
  contents_.Deleter()(contents_.Data(),
                      contents_.ByteLength(),
                      contents_.DeleterData());
}

MaybeLocal<SharedArrayBuffer> SharedArrayBufferMetadata::GetSharedArrayBuffer(
    Environment* env, Local<Context> context) {
  Local<SharedArrayBuffer> obj =
      SharedArrayBuffer::New(env->isolate(),
                             contents_.Data(),
                             contents_.ByteLength());

  if (AssignToSharedArrayBuffer(env, context, obj).IsNothing())
    return MaybeLocal<SharedArrayBuffer>();

  return obj;
}

}  // namespace worker
}  // namespace node