mirror of
https://github.com/penpot/penpot.git
synced 2026-09-07 04:29:31 +00:00
🚑 Use base64 envelope for Uint8Array task results to avoid JSON expansion (#9431)
Resolves #9420 (critical memory usage issue in PROD deployment) When the plugin's ExecuteCodeTaskHandler returns a Uint8Array (e.g. from penpotUtils.exportImage), JSON.stringify previously serialized it as an object with numeric string keys, causing ~10x payload expansion and large peak heap usage on the server side. The plugin now wraps a top-level Uint8Array result in a tagged envelope { __type: "base64", data: <base64> }, and ImageContent.byteData decodes this envelope on the server. The legacy numeric-keyed-object path is retained as a fallback for compatibility with older plugin builds.
This commit is contained in:
parent
0817f13340
commit
c3743930c2
@ -236,6 +236,13 @@ export class ExecuteCodeTaskHandler extends TaskHandler<ExecuteCodeTaskParams> {
|
|||||||
|
|
||||||
console.log("Code execution result:", result);
|
console.log("Code execution result:", result);
|
||||||
|
|
||||||
|
// transform a top-level Uint8Array result into a compact base64 envelope to avoid the
|
||||||
|
// ~10x JSON expansion that occurs when JSON.stringify serializes typed arrays as objects
|
||||||
|
// with numeric string keys (see penpot/penpot#9420)
|
||||||
|
if (result instanceof Uint8Array) {
|
||||||
|
result = ExecuteCodeTaskHandler.encodeBytesAsBase64Envelope(result);
|
||||||
|
}
|
||||||
|
|
||||||
// return result and captured log
|
// return result and captured log
|
||||||
let resultData: ExecuteCodeTaskResultData<any> = {
|
let resultData: ExecuteCodeTaskResultData<any> = {
|
||||||
result: result,
|
result: result,
|
||||||
@ -243,4 +250,23 @@ export class ExecuteCodeTaskHandler extends TaskHandler<ExecuteCodeTaskParams> {
|
|||||||
};
|
};
|
||||||
task.sendSuccess(resultData);
|
task.sendSuccess(resultData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base64-encodes the given bytes and wraps the result in a tagged envelope that the
|
||||||
|
* server side recognizes (see `ImageContent.byteData`).
|
||||||
|
*
|
||||||
|
* @param bytes - the raw binary data to encode
|
||||||
|
* @returns an envelope of the form `{ __type: "base64", data: <base64 string> }`
|
||||||
|
*/
|
||||||
|
private static encodeBytesAsBase64Envelope(bytes: Uint8Array): { __type: "base64"; data: string } {
|
||||||
|
// build the binary string in chunks; calling `String.fromCharCode(...bytes)` directly
|
||||||
|
// would overflow the call stack for large arrays
|
||||||
|
const chunkSize = 0x8000;
|
||||||
|
let binary = "";
|
||||||
|
for (let i = 0; i < bytes.length; i += chunkSize) {
|
||||||
|
const chunk = bytes.subarray(i, i + chunkSize);
|
||||||
|
binary += String.fromCharCode.apply(null, chunk as unknown as number[]);
|
||||||
|
}
|
||||||
|
return { __type: "base64", data: btoa(binary) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,19 +37,26 @@ export class ImageContent implements ImageItem {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function for ensuring a consistent Uint8Array representation of byte data.
|
* Utility function for ensuring a consistent Uint8Array representation of byte data.
|
||||||
* Input can be either a Uint8Array or an object (as obtained from JSON conversion of Uint8Array
|
* Input can be one of:
|
||||||
* from the plugin).
|
* - a `Uint8Array` (already in the desired form);
|
||||||
|
* - a base64 envelope `{ __type: "base64", data: <base64 string> }` produced by the plugin
|
||||||
|
* to avoid the ~10x JSON expansion of typed arrays (see penpot/penpot#9420);
|
||||||
|
* - a numeric-keyed object obtained from `JSON.stringify`-ing a `Uint8Array` (legacy fallback).
|
||||||
*
|
*
|
||||||
* @param data - data as Uint8Array or as object (from JSON conversion of Uint8Array)
|
* @param data - data as `Uint8Array`, base64 envelope, or numeric-keyed object
|
||||||
* @return data as Uint8Array
|
* @return data as `Uint8Array`
|
||||||
*/
|
*/
|
||||||
public static byteData(data: Uint8Array | object): Uint8Array {
|
public static byteData(data: Uint8Array | object): Uint8Array {
|
||||||
if (typeof data === "object") {
|
if (data instanceof Uint8Array) {
|
||||||
// convert object (as obtained from JSON conversion of Uint8Array) back to Uint8Array
|
|
||||||
return new Uint8Array(Object.values(data) as number[]);
|
|
||||||
} else {
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
// recognize the base64 envelope produced by the plugin's ExecuteCodeTaskHandler
|
||||||
|
const envelope = data as { __type?: unknown; data?: unknown };
|
||||||
|
if (envelope.__type === "base64" && typeof envelope.data === "string") {
|
||||||
|
return new Uint8Array(Buffer.from(envelope.data, "base64"));
|
||||||
|
}
|
||||||
|
// legacy fallback: object (as obtained from JSON conversion of Uint8Array) back to Uint8Array
|
||||||
|
return new Uint8Array(Object.values(data) as number[]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user