or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.mdload-balancing.mdperformance-monitoring.mdpool-management.mdtask-cancellation.mdtask-queues.mdtransferable-objects.md

transferable-objects.mddocs/

0

# Transferable Objects

1

2

Efficient data transfer mechanisms using structured cloning and transferable objects for optimal performance with large data sets.

3

4

## Capabilities

5

6

### Piscina.move Function

7

8

Primary utility for marking objects as transferable to optimize data transfer between main thread and workers.

9

10

```typescript { .api }

11

/**

12

* Mark objects for efficient transfer to worker threads

13

* Transfers ownership instead of copying data

14

* @param val - Object to transfer (ArrayBuffer, MessagePort, etc.)

15

* @returns Wrapped transferable object

16

*/

17

function move(

18

val: Transferable | TransferListItem | ArrayBufferView | ArrayBuffer | MessagePort

19

): any;

20

```

21

22

**Usage Examples:**

23

24

```typescript

25

import Piscina from "piscina";

26

27

const pool = new Piscina({ filename: "worker.js" });

28

29

// Transfer ArrayBuffer efficiently

30

const buffer = new ArrayBuffer(1024 * 1024); // 1MB buffer

31

const transferableBuffer = Piscina.move(buffer);

32

33

await pool.run({

34

data: transferableBuffer,

35

operation: "processLargeData"

36

});

37

38

// buffer is now unusable in main thread (ownership transferred)

39

console.log(buffer.byteLength); // 0 - buffer is detached

40

41

// Transfer typed arrays

42

const uint8Array = new Uint8Array(1000);

43

uint8Array.fill(42);

44

45

await pool.run({

46

data: Piscina.move(uint8Array), // Transfers underlying ArrayBuffer

47

operation: "processTypedArray"

48

});

49

50

// Transfer multiple objects

51

const buffer1 = new ArrayBuffer(512);

52

const buffer2 = new ArrayBuffer(256);

53

54

await pool.run({

55

buffers: [Piscina.move(buffer1), Piscina.move(buffer2)],

56

operation: "processMultipleBuffers"

57

});

58

```

59

60

### Transferable Interface

61

62

Interface for objects that can be efficiently transferred between threads.

63

64

```typescript { .api }

65

/**

66

* Interface for transferable objects

67

*/

68

interface Transferable {

69

/** Internal transferable object reference */

70

readonly [transferableSymbol]: object;

71

72

/** Internal value object reference */

73

readonly [valueSymbol]: object;

74

}

75

```

76

77

### Transfer List Types

78

79

Type definitions for transferable object collections.

80

81

```typescript { .api }

82

/**

83

* Array of transferable objects

84

* Extracted from MessagePort.postMessage signature

85

*/

86

type TransferList = MessagePort extends {

87

postMessage: (value: any, transferList: infer T) => any;

88

} ? T : never;

89

90

/**

91

* Individual transferable object type

92

*/

93

type TransferListItem = TransferList extends Array<infer T> ? T : never;

94

```

95

96

### Manual Transfer Lists

97

98

Use transfer lists directly in run options for fine-grained control.

99

100

```typescript { .api }

101

interface RunOptions {

102

/** Objects to transfer ownership to worker (for performance) */

103

transferList?: TransferList;

104

}

105

```

106

107

**Usage Examples:**

108

109

```typescript

110

import Piscina from "piscina";

111

112

const pool = new Piscina({ filename: "worker.js" });

113

114

// Manual transfer list (without Piscina.move)

115

const buffer = new ArrayBuffer(2048);

116

const result = await pool.run(

117

{ data: buffer, operation: "process" },

118

{ transferList: [buffer] }

119

);

120

121

// Mixed approach: some moved, some in transfer list

122

const buffer1 = new ArrayBuffer(1024);

123

const buffer2 = new ArrayBuffer(1024);

124

const port = new MessageChannel().port1;

125

126

await pool.run(

127

{

128

moved: Piscina.move(buffer1), // Automatic transfer

129

manual: buffer2, // Manual transfer

130

port: port // Manual transfer

131

},

132

{ transferList: [buffer2, port] } // Explicit transfer list

133

);

134

```

135

136

### Transferable Object Detection

137

138

Utilities for working with transferable objects.

139

140

```typescript { .api }

141

/**

142

* Check if object implements Transferable interface

143

* @param value - Object to check

144

* @returns True if object is transferable

145

*/

146

function isTransferable(value: unknown): boolean;

147

148

/**

149

* Check if object is marked as movable by Piscina.move()

150

* @param value - Object to check

151

* @returns True if object was processed by move()

152

*/

153

function isMovable(value: any): boolean;

154

```

155

156

**Usage Examples:**

157

158

```typescript

159

import { isTransferable, isMovable, move } from "piscina";

160

161

const buffer = new ArrayBuffer(1024);

162

const movedBuffer = move(buffer);

163

const regularObject = { data: "test" };

164

165

console.log(isTransferable(buffer)); // false (not wrapped)

166

console.log(isTransferable(movedBuffer)); // true (wrapped by move)

167

console.log(isMovable(movedBuffer)); // true (marked as movable)

168

console.log(isTransferable(regularObject)); // false

169

console.log(isMovable(regularObject)); // false

170

171

// Use in conditional logic

172

function processData(data: any) {

173

if (isMovable(data)) {

174

console.log("Data will be transferred efficiently");

175

} else {

176

console.log("Data will be cloned (slower for large objects)");

177

}

178

}

179

```

180

181

### Supported Transferable Types

182

183

Objects that can be transferred efficiently between threads:

184

185

```typescript { .api }

186

// Native transferable types (partial list)

187

type NativeTransferableTypes =

188

| ArrayBuffer

189

| MessagePort

190

| ReadableStream

191

| WritableStream

192

| TransformStream

193

| AudioData

194

| ImageBitmap

195

| OffscreenCanvas;

196

197

// Typed array views (transfer underlying ArrayBuffer)

198

type TypedArrayTypes =

199

| Int8Array

200

| Uint8Array

201

| Uint8ClampedArray

202

| Int16Array

203

| Uint16Array

204

| Int32Array

205

| Uint32Array

206

| Float32Array

207

| Float64Array

208

| BigInt64Array

209

| BigUint64Array;

210

```

211

212

**Usage Examples:**

213

214

```typescript

215

import Piscina from "piscina";

216

217

const pool = new Piscina({ filename: "worker.js" });

218

219

// ArrayBuffer transfer

220

const arrayBuffer = new ArrayBuffer(1024);

221

await pool.run({ buffer: Piscina.move(arrayBuffer) });

222

223

// Typed array transfer (transfers underlying ArrayBuffer)

224

const float32Array = new Float32Array(256);

225

await pool.run({ floats: Piscina.move(float32Array) });

226

227

// MessagePort transfer

228

const { port1, port2 } = new MessageChannel();

229

await pool.run({

230

port: Piscina.move(port1),

231

setupCommunication: true

232

});

233

234

// Multiple transfers

235

const data = {

236

buffer1: Piscina.move(new ArrayBuffer(512)),

237

buffer2: Piscina.move(new ArrayBuffer(256)),

238

array: Piscina.move(new Uint8Array(128))

239

};

240

await pool.run(data);

241

```

242

243

### Performance Considerations

244

245

#### When to Use Transferable Objects

246

247

```typescript

248

import Piscina from "piscina";

249

250

const pool = new Piscina({ filename: "worker.js" });

251

252

// ✅ Good: Large data structures

253

const largeBuffer = new ArrayBuffer(10 * 1024 * 1024); // 10MB

254

await pool.run({

255

data: Piscina.move(largeBuffer), // Efficient transfer

256

operation: "processLargeData"

257

});

258

259

// ✅ Good: Frequent transfers of medium-sized data

260

const imageData = new Uint8Array(1920 * 1080 * 4); // 8MB image

261

await pool.run({

262

pixels: Piscina.move(imageData),

263

operation: "applyFilter"

264

});

265

266

// ❌ Avoid: Small objects (overhead not worth it)

267

const smallArray = new Uint8Array(10);

268

await pool.run({

269

data: smallArray, // Just clone, don't transfer

270

operation: "processSmall"

271

});

272

273

// ❌ Avoid: Objects you need to keep using

274

const buffer = new ArrayBuffer(1024);

275

// Don't move if you need buffer later

276

await pool.run({

277

data: { ...buffer }, // Clone instead

278

operation: "process"

279

});

280

// buffer is still usable

281

```

282

283

#### Transfer vs Clone Performance

284

285

```typescript

286

import Piscina from "piscina";

287

288

const pool = new Piscina({ filename: "worker.js" });

289

290

async function performanceComparison() {

291

const largeBuffer = new ArrayBuffer(50 * 1024 * 1024); // 50MB

292

293

// Measure cloning performance

294

console.time('Clone Transfer');

295

await pool.run({

296

data: largeBuffer, // Will be cloned

297

operation: "benchmark"

298

});

299

console.timeEnd('Clone Transfer');

300

301

// Measure transfer performance

302

const anotherBuffer = new ArrayBuffer(50 * 1024 * 1024);

303

console.time('Move Transfer');

304

await pool.run({

305

data: Piscina.move(anotherBuffer), // Will be transferred

306

operation: "benchmark"

307

});

308

console.timeEnd('Move Transfer'); // Should be much faster

309

}

310

```

311

312

### Worker-Side Handling

313

314

How workers receive and process transferred objects:

315

316

**Worker file (worker.js):**

317

318

```javascript

319

// Worker receives transferred objects normally

320

module.exports = function(data) {

321

// data.buffer is now owned by this worker thread

322

const { buffer, operation } = data;

323

324

if (operation === "processLargeData") {

325

// Work with transferred ArrayBuffer

326

const view = new Uint8Array(buffer);

327

328

// Process data...

329

for (let i = 0; i < view.length; i++) {

330

view[i] = view[i] * 2;

331

}

332

333

// Can transfer back to main thread

334

return {

335

result: "processed",

336

// Transfer modified buffer back

337

processedData: buffer // Will be transferred back

338

};

339

}

340

341

return { result: "complete" };

342

};

343

```

344

345

### Symbols and Constants

346

347

Access transferable-related symbols for advanced usage.

348

349

```typescript { .api }

350

class Piscina {

351

/** Symbol used to mark transferable objects */

352

static readonly transferableSymbol: symbol;

353

354

/** Symbol used to access transferable values */

355

static readonly valueSymbol: symbol;

356

357

/** Symbol used for queue options */

358

static readonly queueOptionsSymbol: symbol;

359

}

360

361

// Named exports for convenience

362

const transferableSymbol: typeof Piscina.transferableSymbol;

363

const valueSymbol: typeof Piscina.valueSymbol;

364

const queueOptionsSymbol: typeof Piscina.queueOptionsSymbol;

365

```

366

367

**Usage Examples:**

368

369

```typescript

370

import { transferableSymbol, valueSymbol } from "piscina";

371

372

// Create custom transferable wrapper

373

class CustomTransferable {

374

constructor(private data: ArrayBuffer) {}

375

376

get [transferableSymbol]() {

377

return this.data;

378

}

379

380

get [valueSymbol]() {

381

return this.data;

382

}

383

}

384

385

// Use custom transferable

386

const customData = new CustomTransferable(new ArrayBuffer(1024));

387

await pool.run({ data: customData });

388

```

389

390

### Best Practices

391

392

#### Memory Management

393

394

```typescript

395

import Piscina from "piscina";

396

397

const pool = new Piscina({ filename: "worker.js" });

398

399

// ✅ Good: Clear transfer intent

400

async function processLargeFile(fileBuffer: ArrayBuffer) {

401

try {

402

const result = await pool.run({

403

data: Piscina.move(fileBuffer),

404

operation: "parseFile"

405

});

406

407

// fileBuffer is now detached, don't use it

408

return result;

409

} catch (error) {

410

// Handle errors - buffer may still be detached

411

console.error("Processing failed:", error);

412

throw error;

413

}

414

}

415

416

// ✅ Good: Keep reference if needed later

417

async function processWithBackup(originalBuffer: ArrayBuffer) {

418

// Clone for transfer, keep original

419

const bufferCopy = originalBuffer.slice();

420

421

const result = await pool.run({

422

data: Piscina.move(bufferCopy),

423

operation: "process"

424

});

425

426

// originalBuffer is still usable

427

return { result, originalSize: originalBuffer.byteLength };

428

}

429

```

430

431

#### Error Handling

432

433

```typescript

434

import Piscina from "piscina";

435

436

const pool = new Piscina({ filename: "worker.js" });

437

438

async function safeTransfer(buffer: ArrayBuffer) {

439

// Check if buffer is already detached

440

if (buffer.byteLength === 0) {

441

throw new Error("Buffer is already detached");

442

}

443

444

try {

445

const result = await pool.run({

446

data: Piscina.move(buffer),

447

operation: "process"

448

});

449

return result;

450

} catch (error) {

451

// Buffer is detached even if task failed

452

console.warn("Task failed, buffer is detached");

453

throw error;

454

}

455

}

456

```