UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

164 lines (137 loc) 5.42 kB
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/child_process/serialization.js import { Buffer } from "nstdlib/lib/buffer"; import { StringDecoder } from "nstdlib/lib/string_decoder"; import * as v8 from "nstdlib/lib/v8"; import { isArrayBufferView } from "nstdlib/lib/internal/util/types"; import * as assert from "nstdlib/lib/internal/assert"; import { streamBaseState, kLastWriteWasAsync, } from "nstdlib/stub/binding/stream_wrap"; const kMessageBuffer = Symbol("kMessageBuffer"); const kMessageBufferSize = Symbol("kMessageBufferSize"); const kJSONBuffer = Symbol("kJSONBuffer"); const kStringDecoder = Symbol("kStringDecoder"); // Extend V8's serializer APIs to give more JSON-like behaviour in // some cases; in particular, for native objects this serializes them the same // way that JSON does rather than throwing an exception. const kArrayBufferViewTag = 0; const kNotArrayBufferViewTag = 1; class ChildProcessSerializer extends v8.DefaultSerializer { _writeHostObject(object) { if (isArrayBufferView(object)) { this.writeUint32(kArrayBufferViewTag); return super._writeHostObject(object); } this.writeUint32(kNotArrayBufferViewTag); this.writeValue({ ...object }); } } class ChildProcessDeserializer extends v8.DefaultDeserializer { _readHostObject() { const tag = this.readUint32(); if (tag === kArrayBufferViewTag) return super._readHostObject(); assert(tag === kNotArrayBufferViewTag); return this.readValue(); } } // Messages are parsed in either of the following formats: // - Newline-delimited JSON, or // - V8-serialized buffers, prefixed with their length as a big endian uint32 // (aka 'advanced') const advanced = { initMessageChannel(channel) { channel[kMessageBuffer] = []; channel[kMessageBufferSize] = 0; channel.buffering = false; }, *parseChannelMessages(channel, readData) { if (readData.length === 0) return; Array.prototype.push.call(channel[kMessageBuffer], readData); channel[kMessageBufferSize] += readData.length; // Index 0 should always be present because we just pushed data into it. let messageBufferHead = channel[kMessageBuffer][0]; while (messageBufferHead.length >= 4) { // We call `readUInt32BE` manually here, because this is faster than first converting // it to a buffer and using `readUInt32BE` on that. const fullMessageSize = ((messageBufferHead[0] << 24) | (messageBufferHead[1] << 16) | (messageBufferHead[2] << 8) | messageBufferHead[3]) + 4; if (channel[kMessageBufferSize] < fullMessageSize) break; const concatenatedBuffer = channel[kMessageBuffer].length === 1 ? channel[kMessageBuffer][0] : Buffer.concat(channel[kMessageBuffer], channel[kMessageBufferSize]); const deserializer = new ChildProcessDeserializer( TypedArrayPrototypeSubarray(concatenatedBuffer, 4, fullMessageSize), ); messageBufferHead = TypedArrayPrototypeSubarray( concatenatedBuffer, fullMessageSize, ); channel[kMessageBufferSize] = messageBufferHead.length; channel[kMessageBuffer] = channel[kMessageBufferSize] !== 0 ? [messageBufferHead] : []; deserializer.readHeader(); yield deserializer.readValue(); } channel.buffering = channel[kMessageBufferSize] > 0; }, writeChannelMessage(channel, req, message, handle) { const ser = new ChildProcessSerializer(); // Add 4 bytes, to later populate with message length ser.writeRawBytes(Buffer.allocUnsafe(4)); ser.writeHeader(); ser.writeValue(message); const serializedMessage = ser.releaseBuffer(); const serializedMessageLength = serializedMessage.length - 4; serializedMessage.set( [ (serializedMessageLength >> 24) & 0xff, (serializedMessageLength >> 16) & 0xff, (serializedMessageLength >> 8) & 0xff, serializedMessageLength & 0xff, ], 0, ); const result = channel.writeBuffer(req, serializedMessage, handle); // Mirror what stream_base_commons.js does for Buffer retention. if (streamBaseState[kLastWriteWasAsync]) req.buffer = serializedMessage; return result; }, }; const json = { initMessageChannel(channel) { channel[kJSONBuffer] = ""; channel[kStringDecoder] = undefined; }, *parseChannelMessages(channel, readData) { if (readData.length === 0) return; if (channel[kStringDecoder] === undefined) channel[kStringDecoder] = new StringDecoder("utf8"); const chunks = String.prototype.split.call( channel[kStringDecoder].write(readData), "\n", ); const numCompleteChunks = chunks.length - 1; // Last line does not have trailing linebreak const incompleteChunk = chunks[numCompleteChunks]; if (numCompleteChunks === 0) { channel[kJSONBuffer] += incompleteChunk; } else { chunks[0] = channel[kJSONBuffer] + chunks[0]; for (let i = 0; i < numCompleteChunks; i++) yield JSONParse(chunks[i]); channel[kJSONBuffer] = incompleteChunk; } channel.buffering = channel[kJSONBuffer].length !== 0; }, writeChannelMessage(channel, req, message, handle) { const string = JSONStringify(message) + "\n"; return channel.writeUtf8String(req, string, handle); }, }; export { advanced }; export { json };