Skip to content

Commit fcf2fe5

Browse files
committed
refactor: aborted run with return null
1 parent 66ed6ce commit fcf2fe5

File tree

2 files changed

+37
-12
lines changed

2 files changed

+37
-12
lines changed

src/mcp/server.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ export class ActorsMcpServer {
548548

549549
try {
550550
log.info('Calling Actor', { actorName: actorTool.actorFullName, input: args });
551-
const { runId, datasetId, items } = await callActorGetDataset(
551+
const result = await callActorGetDataset(
552552
actorTool.actorFullName,
553553
args,
554554
apifyToken as string,
@@ -557,6 +557,13 @@ export class ActorsMcpServer {
557557
extra.signal,
558558
);
559559

560+
if (!result) {
561+
// If the actor was aborted by the client, we don't want to return anything
562+
return { };
563+
}
564+
565+
const { runId, datasetId, items } = result;
566+
560567
const content = [
561568
{ type: 'text', text: `Actor finished with runId: ${runId}, datasetId ${datasetId}` },
562569
];

src/tools/actor.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,26 +49,38 @@ export type CallActorGetDatasetResult = {
4949
* @param {unknown} input - The input to pass to the actor.
5050
* @param {string} apifyToken - The Apify token to use for authentication.
5151
* @param {ProgressTracker} progressTracker - Optional progress tracker for real-time updates.
52-
* @returns {Promise<CallActorGetDatasetResult>} - A promise that resolves to an object containing the actor run and dataset items.
52+
* @param {AbortSignal} abortSignal - Optional abort signal to cancel the actor run.
53+
* @returns {Promise<CallActorGetDatasetResult | null>} - A promise that resolves to an object containing the actor run and dataset items.
5354
* @throws {Error} - Throws an error if the `APIFY_TOKEN` is not set
5455
*/
56+
export async function callActorGetDataset(
57+
actorName: string,
58+
input: unknown,
59+
apifyToken: string,
60+
callOptions: ActorCallOptions | undefined,
61+
progressTracker?: ProgressTracker | null,
62+
): Promise<CallActorGetDatasetResult>; // Without abort signal Result or Error is returned
63+
export async function callActorGetDataset(
64+
actorName: string,
65+
input: unknown,
66+
apifyToken: string,
67+
callOptions: ActorCallOptions | undefined,
68+
progressTracker: ProgressTracker | null,
69+
abortSignal: AbortSignal,
70+
): Promise<CallActorGetDatasetResult | null>; // With abort signal, null is returned if the actor was aborted by the client
5571
export async function callActorGetDataset(
5672
actorName: string,
5773
input: unknown,
5874
apifyToken: string,
5975
callOptions: ActorCallOptions | undefined = undefined,
6076
progressTracker?: ProgressTracker | null,
6177
abortSignal?: AbortSignal,
62-
): Promise<CallActorGetDatasetResult> {
78+
): Promise<CallActorGetDatasetResult | null> {
79+
const CLIENT_ABORT = Symbol('CLIENT_ABORT'); // Just internal symbol to identify client abort
6380
try {
6481
const client = new ApifyClient({ token: apifyToken });
6582
const actorClient = client.actor(actorName);
6683

67-
// Check if already aborted
68-
if (abortSignal?.aborted) {
69-
throw new Error('Operation cancelled');
70-
}
71-
7284
// Start the actor run
7385
const actorRun: ActorRun = await actorClient.start(input, callOptions);
7486

@@ -78,25 +90,31 @@ export async function callActorGetDataset(
7890
}
7991

8092
// Create abort promise that handles both API abort and race rejection
81-
const abortPromise = async () => new Promise<never>((_, reject) => {
93+
const abortPromise = async () => new Promise<typeof CLIENT_ABORT>((resolve) => {
8294
abortSignal?.addEventListener('abort', async () => {
8395
// Abort the actor run via API
8496
try {
8597
await client.run(actorRun.id).abort({ gracefully: true });
8698
} catch (e) {
87-
log.debug('Error aborting Actor run', { error: e, runId: actorRun.id });
99+
log.error('Error aborting Actor run', { error: e, runId: actorRun.id });
88100
}
89101
// Reject to stop waiting
90-
reject(new Error('Operation cancelled'));
102+
resolve(CLIENT_ABORT);
91103
}, { once: true });
92104
});
93105

94106
// Wait for completion or cancellation
95-
const completedRun = await Promise.race([
107+
const potentialAbortedRun = await Promise.race([
96108
client.run(actorRun.id).waitForFinish(),
97109
...(abortSignal ? [abortPromise()] : []),
98110
]);
99111

112+
if (potentialAbortedRun === CLIENT_ABORT) {
113+
log.info('Actor run aborted by client', { actorName, input });
114+
return null;
115+
}
116+
const completedRun = potentialAbortedRun as ActorRun;
117+
100118
// Process the completed run
101119
const dataset = client.dataset(completedRun.defaultDatasetId);
102120
const [items, defaultBuild] = await Promise.all([

0 commit comments

Comments
 (0)