Skip to content

Commit

Permalink
Merge pull request #477 from mrmlnc/ISSUE-435_abort_signal
Browse files Browse the repository at this point in the history
Support AbortSignal to abort the processing
  • Loading branch information
mrmlnc authored Jan 4, 2025
2 parents bbc7d07 + c994937 commit 665be46
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 1 deletion.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ This package provides methods for traversing the file system and returning pathn
* [ignore](#ignore)
* [suppressErrors](#suppresserrors)
* [throwErrorOnBrokenSymbolicLink](#throwerroronbrokensymboliclink)
* [signal](#signal)
* [Output control](#output-control)
* [absolute](#absolute)
* [markDirectories](#markdirectories)
Expand Down Expand Up @@ -405,6 +406,15 @@ Throw an error when symbolic link is broken if `true` or safely return `lstat` c

> :book: This option has no effect on errors when reading the symbolic link directory.
#### signal

* Type: [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
* Default: `undefined`

A signal to abort searching for entries on the file system. Works only with asynchronous methods for dynamic and static patterns.

> :book: The abort signal does not interrupt the operation instantly. After the abort signal, there will be a brief period during which the tail of unprocessed but already read directories will be processed. New directories will not be added to the queue for reading, entries found in processed directories will not be emitted. Think of it as a no-op loop because we do not know at what stage of processing the abort signal was triggered.
### Output control

#### absolute
Expand Down
61 changes: 61 additions & 0 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,29 @@ describe('Package', () => {

assert.deepStrictEqual(actual, expected);
});

it('should abort processing dynamic pattern with abort signal', async () => {
const ac = new AbortController();

setTimeout(() => {
ac.abort();
}, 5);

// The globstar pattern is used here to make the call run longer than the settimeout.
const action = fg.glob(['**'], { signal: ac.signal });

await assert.rejects(() => action, { message: 'This operation was aborted' });
});

it('should abort processing static pattern with abort signal', async () => {
const ac = new AbortController();

ac.abort();

const action = fg.glob(['./package.json'], { signal: ac.signal });

await assert.rejects(() => action, { message: 'The operation was aborted' });
});
});

describe('.async', () => {
Expand Down Expand Up @@ -175,6 +198,44 @@ describe('Package', () => {
done();
});
});

it('should abort processing dynamic pattern with abort signal', (done) => {
const ac = new AbortController();

setTimeout(() => {
ac.abort();
}, 5);

// The globstar pattern is used here to make the call run longer than the settimeout.
const steam = fg.globStream(['**'], { signal: ac.signal });

steam.once('error', (error: ErrnoException) => {
assert.strictEqual(error.message, 'This operation was aborted');
done();
});

steam.once('end', () => {
assert.fail('The stream should be aborted');
});
});

it('should abort processing static pattern with abort signal', (done) => {
const ac = new AbortController();

ac.abort();

// The globstar pattern is used here to make the call run longer than the settimeout.
const steam = fg.globStream(['./package.json'], { signal: ac.signal });

steam.once('error', (error: ErrnoException) => {
assert.strictEqual(error.message, 'The operation was aborted');
done();
});

steam.once('end', () => {
assert.fail('The stream should be aborted');
});
});
});

describe('.stream', () => {
Expand Down
1 change: 1 addition & 0 deletions src/providers/provider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ describe('Providers → Provider', () => {
assert.ok(!actual.stats);
assert.ok(actual.throwErrorOnBrokenSymbolicLink === false);
assert.strictEqual(typeof actual.transform, 'function');
assert.strictEqual(actual.signal, undefined);
});

it('should return options for reader with non-global base', () => {
Expand Down
1 change: 1 addition & 0 deletions src/providers/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export abstract class Provider<T> {
stats: this.#settings.stats,
throwErrorOnBrokenSymbolicLink: this.#settings.throwErrorOnBrokenSymbolicLink,
transform: this.entryTransformer.getTransformer(),
signal: this.#settings.signal,
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/readers/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class ReaderStream extends Reader<Readable> implements IReaderStream {
public static(patterns: Pattern[], options: ReaderOptions): Readable {
const filepaths = patterns.map((pattern) => this._getFullEntryPath(pattern));

const stream = new PassThrough({ objectMode: true });
const stream = new PassThrough({ objectMode: true, signal: options.signal });

stream._write = (index: number, _enc, done) => {
this.#getEntry(filepaths[index], patterns[index], options)
Expand Down
1 change: 1 addition & 0 deletions src/settings.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('Settings', () => {
assert.ok(settings.onlyFiles);
assert.ok(settings.unique);
assert.strictEqual(settings.cwd, process.cwd());
assert.strictEqual(settings.signal, undefined);
});

it('should return instance with custom values', () => {
Expand Down
9 changes: 9 additions & 0 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ export interface Options {
* @default true
*/
unique?: boolean;
/**
* A signal to abort searching for entries on the file system.
* Works only with asynchronous methods for dynamic and static patterns.
*
* @default undefined
*/
signal?: AbortSignal;
}

export default class Settings {
Expand All @@ -161,6 +168,7 @@ export default class Settings {
public readonly suppressErrors: boolean;
public readonly throwErrorOnBrokenSymbolicLink: boolean;
public readonly unique: boolean;
public readonly signal?: AbortSignal;

// eslint-disable-next-line complexity
constructor(options: Options = {}) {
Expand All @@ -184,6 +192,7 @@ export default class Settings {
this.suppressErrors = options.suppressErrors ?? false;
this.throwErrorOnBrokenSymbolicLink = options.throwErrorOnBrokenSymbolicLink ?? false;
this.unique = options.unique ?? true;
this.signal = options.signal;

if (this.onlyDirectories) {
this.onlyFiles = false;
Expand Down

0 comments on commit 665be46

Please sign in to comment.