Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server Fn Signal and API route (request.signal) does not actually fire 'abort' event. #3490

Open
cpakken opened this issue Feb 20, 2025 · 3 comments

Comments

@cpakken
Copy link

cpakken commented Feb 20, 2025

Which project does this relate to?

Start

Describe the bug

When canceling a server function or fetch to API Route, either by abortController.abort() or by refreshing / closing the tab to disconnect. The signal's 'abort' event is actually never fired on the server side.

I've made a minimal reproduction here:

https://github.com/cpakken/start-signal-issue

On the terminal (server), it should console.log for 'abort' signal, but the server function will just continue its work. For computationally heavy server functions, you could use this abort event to trip a flag so that it stops any processing mid way through. Or if you pass the abort signal to a fetch called from the server, it never actually cancels it.

This might have to do with H3 Fetch and the way it implements aborts or detects when connection is closed abruptly?

Heres implementation from the minimal reproduction:

import { createServerFn } from '@tanstack/start'

export const fooServerFn = createServerFn({ method: 'POST' }).handler(async ({ signal }) => {
  signal.addEventListener('abort', () => {
    //THIS IS NEVER RUN!!!!
    //This should run when abort controller is called on the client side
    //Or when the client disconnects
    console.log('ABORTED: THIS NEVER RUNS!')
    throw new Error('aborted')
  })

  await new Promise((resolve) => setTimeout(resolve, 2000))

  //Why is this signal already aborted?!? when abort is not called?
  console.log('RETURNED SUCCESS BUT IS Signal aborted?', signal.aborted)

  return {
    data: 'SERVER FN: FOO!',
  }
})
import { json } from '@tanstack/start'
import { createAPIFileRoute } from '@tanstack/start/api'

export const APIRoute = createAPIFileRoute('/api/foo')({
  GET: async ({ request }) => {
    const { signal } = request
    signal.addEventListener('abort', () => {
      //THIS IS NEVER RUN!!!!
      //This should run when abort controller is called on the client side
      //Or when the client disconnects

      console.log('ABORTED: THIS NEVER RUNS!')
      throw new Error('aborted')
    })

    console.log('FETCHING...')

    await new Promise((resolve) => setTimeout(resolve, 2000))

    console.log('RETURNED SUCCESS BUT IS Signal aborted?', signal.aborted)

    return json({
      data: 'API: DATA IS FOO!',
    })
  },
})

I've been using this as my abort signal, but ideally the request.signal or signal passed in from createServerFn should just work

import { getEvent } from 'vinxi/http'

export const createAbortSignal = () => {
  const controller = new AbortController()

  const { res } = getEvent().node

  res.on('close', () => {
    // console.log('RES close')
    if (!res.writableEnded) {
      console.log('aborting')
      controller.abort()
    }
  })

  return controller.signal
}

Your Example Website or App

https://github.com/cpakken/start-signal-issue

Steps to Reproduce the Bug or Issue

https://github.com/cpakken/start-signal-issue

npm run dev or bun dev

Click 'fetch' button and 'abort'
Click 'fetch' button and refresh the tab to disconnect

Expected behavior

In the terminal running Start, you should see the abort event triggering

Screenshots or Videos

No response

Platform

  • OS: Windows 10
  • Browser: Edge 133.0.3065.69

Additional context

No response

@schiller-manuel
Copy link
Contributor

schiller-manuel commented Feb 21, 2025

I think your server function example is incorrect.

When running, I see ABORTED: THIS NEVER RUNS! being logged on the server logs.
However, you throw the error from the signal's event handler; this would NOT influence the execution of the server function.

In this example, throwing an error from the signal handler causes the node process to crash.

Instead, you should synchronize the signal's event handler with the function execution, e.g. as shown in the docs: https://tanstack.com/start/latest/docs/framework/react/server-functions#cancellation

I did not try the API route example, but I suspect it has the same issue.

@cpakken
Copy link
Author

cpakken commented Feb 21, 2025

I'm sorry, but I'm not recieving ABORTED: THIS NEVER RUNS! on my end.
I tried on both node v23 and v22
I updated to the latest version 1.109.2

To be clear, on the client, it will show ABORTED and update, but on the server terminal the abort event is just not triggered.

In this example, throwing an error from the signal handler causes the node process to crash.

Doesn't the docs say that you can throw errors?
https://tanstack.com/start/latest/docs/framework/react/server-functions#throwing-errors

import { createServerFn } from '@tanstack/start'

export const doStuff = createServerFn({ method: 'GET' }).handler(async () => {
  throw new Error('Something went wrong!')
})

// Usage
function Test() {
  try {
    await doStuff()
  } catch (error) {
    console.error(error)
    // {
    //   message: "Something went wrong!",
    //   stack: "Error: Something went wrong!\n    at doStuff (file:///path/to/file.ts:3:3)"
    // }
  }
}

Nevertheless, I've made changes so that its using new Promise instead of async await but no difference. It's just not calling the 'abort' event for me. I don't think reject in new Promise is different than using async and throwing new Error.

I've made the updates in
https://github.com/cpakken/start-signal-issue

@schiller-manuel
Copy link
Contributor

So it seems that signal works as expected for GET server functions, but not for POST

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants