mirror of
https://github.com/penpot/penpot.git
synced 2026-05-09 01:58:46 +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
6a44b19311
commit
362440fead
@ -236,6 +236,13 @@ export class ExecuteCodeTaskHandler extends TaskHandler<ExecuteCodeTaskParams> {
|
||||
|
||||
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
|
||||
let resultData: ExecuteCodeTaskResultData<any> = {
|
||||
result: result,
|
||||
@ -243,4 +250,23 @@ export class ExecuteCodeTaskHandler extends TaskHandler<ExecuteCodeTaskParams> {
|
||||
};
|
||||
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.
|
||||
* Input can be either a Uint8Array or an object (as obtained from JSON conversion of Uint8Array
|
||||
* from the plugin).
|
||||
* Input can be one of:
|
||||
* - 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)
|
||||
* @return data as Uint8Array
|
||||
* @param data - data as `Uint8Array`, base64 envelope, or numeric-keyed object
|
||||
* @return data as `Uint8Array`
|
||||
*/
|
||||
public static byteData(data: Uint8Array | object): Uint8Array {
|
||||
if (typeof data === "object") {
|
||||
// convert object (as obtained from JSON conversion of Uint8Array) back to Uint8Array
|
||||
return new Uint8Array(Object.values(data) as number[]);
|
||||
} else {
|
||||
if (data instanceof Uint8Array) {
|
||||
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