Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
df0ddf04b3 | |||
2e1aa4a8ff | |||
bc09033af0 | |||
22df9dfd94 | |||
d48ef6eb43 | |||
9421c652a2 | |||
a6ab15bf1d | |||
00d1455367 |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartstream",
|
||||
"version": "3.0.20",
|
||||
"version": "3.0.24",
|
||||
"private": false,
|
||||
"description": "simplifies access to node streams",
|
||||
"main": "dist_ts/index.js",
|
||||
@ -25,7 +25,6 @@
|
||||
"@git.zone/tsbuild": "^2.1.66",
|
||||
"@git.zone/tsrun": "^1.2.44",
|
||||
"@git.zone/tstest": "^1.0.84",
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartfile": "^11.0.0",
|
||||
"@push.rocks/tapbundle": "^5.0.15",
|
||||
"@types/node": "^20.9.0"
|
||||
|
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -25,9 +25,6 @@ devDependencies:
|
||||
'@git.zone/tstest':
|
||||
specifier: ^1.0.84
|
||||
version: 1.0.84(@types/node@20.9.0)(sinon@17.0.1)
|
||||
'@push.rocks/smartdelay':
|
||||
specifier: ^3.0.5
|
||||
version: 3.0.5
|
||||
'@push.rocks/smartfile':
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0
|
||||
|
@ -10,39 +10,3 @@ hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
hi+wow
|
||||
|
@ -1,62 +0,0 @@
|
||||
import { SmartDuplex, type ISmartDuplexOptions, StreamWrapper } from '../ts/index.js';
|
||||
|
||||
import * as smartdelay from '@push.rocks/smartdelay';
|
||||
|
||||
async function testBackpressure() {
|
||||
const stream1 = new SmartDuplex({
|
||||
name: 'stream1',
|
||||
objectMode: true,
|
||||
handleBackpressure: true,
|
||||
writeFunction: async (chunk, tools) => {
|
||||
console.log(`processed chunk ${chunk} in stream 1`);
|
||||
return chunk; // Fast processing
|
||||
}
|
||||
});
|
||||
const stream2 = new SmartDuplex({
|
||||
name: 'stream2',
|
||||
objectMode: true,
|
||||
handleBackpressure: true,
|
||||
writeFunction: async (chunk, tools) => {
|
||||
await new Promise(resolve => setTimeout(resolve, 1)); // Slow processing
|
||||
console.log(`processed chunk ${chunk} in stream 2`);
|
||||
return chunk;
|
||||
}
|
||||
}); // This stream processes data more slowly
|
||||
const stream3 = new SmartDuplex({
|
||||
objectMode: true,
|
||||
name: 'stream3',
|
||||
handleBackpressure: true,
|
||||
writeFunction: async (chunk, tools) => {
|
||||
await new Promise(resolve => setTimeout(resolve, 200)); // Slow processing
|
||||
console.log(`processed chunk ${chunk} in stream 3`);
|
||||
}
|
||||
});
|
||||
|
||||
stream1.pipe(stream2).pipe(stream3);
|
||||
|
||||
let backpressured = false;
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const canContinue = stream1.write(`Chunk ${i}`, 'utf8');
|
||||
if (!canContinue) {
|
||||
backpressured = true;
|
||||
console.log(`Backpressure at chunk ${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
stream1.end();
|
||||
|
||||
stream1.on('finish', () => {
|
||||
console.log('Stream 1 finished processing.');
|
||||
});
|
||||
stream2.on('finish', () => {
|
||||
console.log('Stream 2 finished processing.');
|
||||
});
|
||||
stream3.on('finish', () => {
|
||||
console.log('Stream 3 finished processing.');
|
||||
if (!backpressured) {
|
||||
console.log('No backpressure was observed.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testBackpressure();
|
68
test/test.backpressure.ts
Normal file
68
test/test.backpressure.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import { SmartDuplex, type ISmartDuplexOptions, StreamWrapper } from '../ts/index.js';
|
||||
|
||||
tap.test('should run backpressure test', async (toolsArg) => {
|
||||
const done = toolsArg.defer();
|
||||
async function testBackpressure() {
|
||||
const stream1 = new SmartDuplex({
|
||||
name: 'stream1',
|
||||
objectMode: true,
|
||||
writeFunction: async (chunk, tools) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 10)); // Slow processing
|
||||
console.log(`processed chunk ${chunk} in stream 1`);
|
||||
return chunk; // Fast processing
|
||||
},
|
||||
});
|
||||
const stream2 = new SmartDuplex({
|
||||
name: 'stream2',
|
||||
objectMode: true,
|
||||
writeFunction: async (chunk, tools) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 20)); // Slow processing
|
||||
console.log(`processed chunk ${chunk} in stream 2`);
|
||||
// await tools.push(chunk);
|
||||
return chunk;
|
||||
},
|
||||
}); // This stream processes data more slowly
|
||||
const stream3 = new SmartDuplex({
|
||||
objectMode: true,
|
||||
name: 'stream3',
|
||||
writeFunction: async (chunk, tools) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100)); // Slow processing
|
||||
console.log(`processed chunk ${chunk} in stream 3`);
|
||||
},
|
||||
});
|
||||
|
||||
stream1.pipe(stream2).pipe(stream3);
|
||||
|
||||
let backpressured = false;
|
||||
for (let i = 0; i < 200; i++) {
|
||||
const canContinue = stream1.write(`Chunk ${i}`, 'utf8');
|
||||
if (!canContinue) {
|
||||
backpressured = true;
|
||||
console.log(`Backpressure at chunk ${i}`);
|
||||
}
|
||||
}
|
||||
|
||||
stream1.end();
|
||||
|
||||
stream1.on('finish', () => {
|
||||
console.log('Stream 1 finished processing.');
|
||||
});
|
||||
stream2.on('finish', () => {
|
||||
console.log('Stream 2 finished processing.');
|
||||
});
|
||||
stream3.on('finish', () => {
|
||||
console.log('Stream 3 finished processing.');
|
||||
if (!backpressured) {
|
||||
throw new Error('No backpressure was observed.');
|
||||
} else {
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testBackpressure();
|
||||
await done.promise;
|
||||
});
|
||||
|
||||
await tap.start();
|
@ -5,9 +5,7 @@ import * as fs from 'fs';
|
||||
|
||||
tap.test('should create a SmartStream from a Buffer', async () => {
|
||||
const bufferData = Buffer.from('This is a test buffer');
|
||||
const smartStream = SmartDuplex.fromBuffer(bufferData, {
|
||||
handleBackpressure: false,
|
||||
});
|
||||
const smartStream = SmartDuplex.fromBuffer(bufferData, {});
|
||||
|
||||
let receivedData = Buffer.alloc(0);
|
||||
|
||||
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartstream',
|
||||
version: '3.0.20',
|
||||
version: '3.0.24',
|
||||
description: 'simplifies access to node streams'
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { Duplex, type DuplexOptions } from 'stream';
|
||||
|
||||
export interface IStreamTools {
|
||||
truncate: () => void;
|
||||
push: (pipeObject: any) => void;
|
||||
push: (pipeObject: any) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface IStreamWriteFunction<T, rT> {
|
||||
@ -17,7 +17,6 @@ export interface IStreamFinalFunction<rT> {
|
||||
export interface ISmartDuplexOptions<TInput, TOutput> extends DuplexOptions {
|
||||
debug?: boolean;
|
||||
name?: string;
|
||||
handleBackpressure?: boolean;
|
||||
readFunction?: () => Promise<void>;
|
||||
writeFunction?: IStreamWriteFunction<TInput, TOutput>;
|
||||
finalFunction?: IStreamFinalFunction<TOutput>;
|
||||
@ -51,8 +50,9 @@ export class SmartDuplex<TInput = any, TOutput = any> extends Duplex {
|
||||
}
|
||||
|
||||
public async _read(size: number): Promise<void> {
|
||||
await this.backpressuredArray.waitForItems();
|
||||
this.debugLog(`${this.options.name}: read was called`);
|
||||
await this.backpressuredArray.waitForItems();
|
||||
this.debugLog(`${this.options.name}: successfully waited for items.`);
|
||||
if (this.options.readFunction) {
|
||||
await this.options.readFunction();
|
||||
}
|
||||
@ -79,8 +79,13 @@ export class SmartDuplex<TInput = any, TOutput = any> extends Duplex {
|
||||
isTruncated = true;
|
||||
callback();
|
||||
},
|
||||
push: (pushArg: TOutput) => {
|
||||
this.backpressuredArray.push(pushArg);
|
||||
push: async (pushArg: TOutput) => {
|
||||
const canPushMore = this.backpressuredArray.push(pushArg);
|
||||
if (!canPushMore) {
|
||||
this.debugLog(`${this.options.name}: cannot push more`);
|
||||
await this.backpressuredArray.waitForSpace();
|
||||
this.debugLog(`${this.options.name}: can push more again`);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -92,12 +97,7 @@ export class SmartDuplex<TInput = any, TOutput = any> extends Duplex {
|
||||
return;
|
||||
}
|
||||
if (modifiedChunk) {
|
||||
const canPushMore = this.backpressuredArray.push(modifiedChunk);
|
||||
if (!canPushMore) {
|
||||
this.debugLog(`${this.options.name}: cannot push more`);
|
||||
await this.backpressuredArray.waitForSpace();
|
||||
this.debugLog(`${this.options.name}: can push more again`);
|
||||
}
|
||||
await tools.push(modifiedChunk);
|
||||
}
|
||||
callback();
|
||||
writeDeferred.resolve();
|
||||
@ -114,21 +114,23 @@ export class SmartDuplex<TInput = any, TOutput = any> extends Duplex {
|
||||
if (this.options.finalFunction) {
|
||||
const tools: IStreamTools = {
|
||||
truncate: () => callback(),
|
||||
push: (pipeObject) => this.push(pipeObject),
|
||||
push: async (pipeObject) => {
|
||||
this.backpressuredArray.push(pipeObject);
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const finalChunk = await this.options.finalFunction(tools);
|
||||
if (finalChunk) {
|
||||
this.push(finalChunk);
|
||||
this.backpressuredArray.push(finalChunk);
|
||||
}
|
||||
} catch (err) {
|
||||
this.push(null);
|
||||
this.backpressuredArray.push(null);
|
||||
callback(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.push(null);
|
||||
this.backpressuredArray.push(null);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user