Skip to content

Commit 33e2913

Browse files
committed
src: introduce PullAll method to speed up blob.text/arrayBuffer
Refs: nodejs/performance#118
1 parent f16f41c commit 33e2913

File tree

3 files changed

+103
-22
lines changed

3 files changed

+103
-22
lines changed

lib/internal/blob.js

+11-22
Original file line numberDiff line numberDiff line change
@@ -274,28 +274,17 @@ class Blob {
274274

275275
const { promise, resolve, reject } = createDeferredPromise();
276276
const reader = this[kHandle].getReader();
277-
const buffers = [];
278-
const readNext = () => {
279-
reader.pull((status, buffer) => {
280-
if (status === 0) {
281-
// EOS, concat & resolve
282-
// buffer should be undefined here
283-
resolve(concat(buffers));
284-
return;
285-
} else if (status < 0) {
286-
// The read could fail for many different reasons when reading
287-
// from a non-memory resident blob part (e.g. file-backed blob).
288-
// The error details the system error code.
289-
const error = lazyDOMException('The blob could not be read', 'NotReadableError');
290-
reject(error);
291-
return;
292-
}
293-
if (buffer !== undefined)
294-
buffers.push(buffer);
295-
queueMicrotask(() => readNext());
296-
});
297-
};
298-
readNext();
277+
reader.pullAll((status, buffer) => {
278+
if (status === 0) {
279+
resolve(buffer);
280+
} else if (status < 0) {
281+
// The read could fail for many different reasons when reading
282+
// from a non-memory resident blob part (e.g. file-backed blob).
283+
// The error details the system error code.
284+
const error = lazyDOMException('The blob could not be read', 'NotReadableError');
285+
reject(error);
286+
}
287+
})
299288
return promise;
300289
}
301290

src/node_blob.cc

+91
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ Local<FunctionTemplate> Blob::Reader::GetConstructorTemplate(Environment* env) {
291291
BaseObject::kInternalFieldCount);
292292
tmpl->SetClassName(FIXED_ONE_BYTE_STRING(env->isolate(), "BlobReader"));
293293
SetProtoMethod(env->isolate(), tmpl, "pull", Pull);
294+
SetProtoMethod(env->isolate(), tmpl, "pullAll", PullAll);
294295
env->set_blob_reader_constructor_template(tmpl);
295296
}
296297
return tmpl;
@@ -379,6 +380,95 @@ void Blob::Reader::Pull(const FunctionCallbackInfo<Value>& args) {
379380
std::move(next), node::bob::OPTIONS_END, nullptr, 0));
380381
}
381382

383+
void Blob::Reader::PullAll(const FunctionCallbackInfo<Value>& args) {
384+
Environment* env = Environment::GetCurrent(args);
385+
Blob::Reader* reader;
386+
ASSIGN_OR_RETURN_UNWRAP(&reader, args.Holder());
387+
388+
CHECK(args[0]->IsFunction());
389+
Local<Function> fn = args[0].As<Function>();
390+
CHECK(!fn->IsConstructor());
391+
392+
if (reader->eos_) {
393+
Local<Value> arg = Int32::New(env->isolate(), bob::STATUS_EOS);
394+
reader->MakeCallback(fn, 1, &arg);
395+
return args.GetReturnValue().Set(bob::STATUS_EOS);
396+
}
397+
398+
struct View {
399+
std::shared_ptr<BackingStore> store;
400+
size_t length;
401+
size_t offset = 0;
402+
};
403+
404+
struct Impl {
405+
BaseObjectPtr<Blob::Reader> reader;
406+
Global<Function> callback;
407+
Environment* env;
408+
size_t total = 0;
409+
std::vector<View> views;
410+
int status = 1;
411+
};
412+
413+
Impl* impl = new Impl();
414+
impl->reader = BaseObjectPtr<Blob::Reader>(reader);
415+
impl->callback.Reset(env->isolate(), fn);
416+
impl->env = env;
417+
418+
auto next = [impl](int status,
419+
const DataQueue::Vec* vecs,
420+
size_t count,
421+
bob::Done doneCb) mutable {
422+
Environment* env = impl->env;
423+
if (status == bob::STATUS_EOS) impl->reader->eos_ = true;
424+
425+
if (count > 0) {
426+
// Copy the returns vectors into a single ArrayBuffer.
427+
size_t total = 0;
428+
for (size_t n = 0; n < count; n++) total += vecs[n].len;
429+
430+
std::shared_ptr<BackingStore> store =
431+
v8::ArrayBuffer::NewBackingStore(env->isolate(), total);
432+
auto ptr = static_cast<uint8_t*>(store->Data());
433+
for (size_t n = 0; n < count; n++) {
434+
std::copy(vecs[n].base, vecs[n].base + vecs[n].len, ptr);
435+
ptr += vecs[n].len;
436+
}
437+
// Since we copied the data buffers, signal that we're done with them.
438+
std::move(doneCb)(0);
439+
impl->views.push_back(View{store, total});
440+
impl->total += total;
441+
}
442+
443+
impl->status = status;
444+
return;
445+
};
446+
447+
while (impl->status > 0) {
448+
impl->reader->inner_->Pull(
449+
next, node::bob::OPTIONS_END, nullptr, 0);
450+
};
451+
452+
std::shared_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
453+
env->isolate(), impl->total);
454+
auto ptr = static_cast<uint8_t*>(store->Data());
455+
for (size_t n = 0; n < impl->views.size(); n++) {
456+
uint8_t* from =
457+
static_cast<uint8_t*>(impl->views[n].store->Data()) + impl->views[n].offset;
458+
std::copy(from, from + impl->views[n].length, ptr);
459+
ptr += impl->views[n].length;
460+
}
461+
462+
Local<Value> argv[2] = {
463+
Int32::New(env->isolate(), impl->status),
464+
ArrayBuffer::New(env->isolate(), store),
465+
};
466+
467+
impl->reader->MakeCallback(fn, arraysize(argv), argv);
468+
auto dropMe = std::unique_ptr<Impl>(impl);
469+
args.GetReturnValue().Set(impl->status);
470+
}
471+
382472
BaseObjectPtr<BaseObject>
383473
Blob::BlobTransferData::Deserialize(
384474
Environment* env,
@@ -560,6 +650,7 @@ void Blob::RegisterExternalReferences(ExternalReferenceRegistry* registry) {
560650
registry->Register(Blob::GetDataObject);
561651
registry->Register(Blob::RevokeObjectURL);
562652
registry->Register(Blob::Reader::Pull);
653+
registry->Register(Blob::Reader::PullAll);
563654
registry->Register(Concat);
564655
registry->Register(BlobFromFilePath);
565656
}

src/node_blob.h

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class Blob : public BaseObject {
8282
static BaseObjectPtr<Reader> Create(Environment* env,
8383
BaseObjectPtr<Blob> blob);
8484
static void Pull(const v8::FunctionCallbackInfo<v8::Value>& args);
85+
static void PullAll(const v8::FunctionCallbackInfo<v8::Value>& args);
8586

8687
explicit Reader(Environment* env,
8788
v8::Local<v8::Object> obj,

0 commit comments

Comments
 (0)