tus / tus/tus-node-server

@tus/s3-store: failed part upload crashes entire server

Open
#722 3 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
TypeScript
Stars
1.1k
Forks
228
Avg merge
5m
Merged PRs (30d)
1

Description

When uploadPart or uploadIncompletePart throws (is rejected) entire server crashes. This happens often with Scaleway Object Storage as their service fails with error 500 with non-informative console message An error was encountered in a non-retryable streaming request. from AWS library. After listening for unhandledRejection event I got some more information.

Unhandled Rejection at: Promise {
  <rejected> S3ServiceException [InternalError]: We encountered an internal error. Please try again.
      at throwDefaultError (/app/node_modules/@smithy/smithy-client/dist-cjs/index.js:867:20)
      at /app/node_modules/@smithy/smithy-client/dist-cjs/index.js:876:5
      at de_CommandError (/app/node_modules/@aws-sdk/client-s3/dist-cjs/index.js:4965:14)
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at async /app/node_modules/@smithy/middleware-serde/dist-cjs/index.js:35:20
      at async /app/node_modules/@aws-sdk/middleware-sdk-s3/dist-cjs/index.js:483:18
      at async /app/node_modules/@smithy/middleware-retry/dist-cjs/index.js:321:38
      at async /app/node_modules/@aws-sdk/middleware-flexible-checksums/dist-cjs/index.js:315:18
      at async /app/node_modules/@aws-sdk/middleware-sdk-s3/dist-cjs/index.js:109:22
      at async /app/node_modules/@aws-sdk/middleware-sdk-s3/dist-cjs/index.js:136:14 {
    '$fault': 'client',
    '$metadata': {
      httpStatusCode: 500,
      requestId: 'txg5c09a7e2fbe14ba89375-0067aa0421',
      extendedRequestId: 'txg5c09a7e2fbe14ba89375-0067aa0421',
      cfId: undefined
    },
    Code: 'InternalError',
    RequestId: 'txg5c09a7e2fbe14ba89375-0067aa0421',
    HostId: 'txg5c09a7e2fbe14ba89375-0067aa0421',
    Resource: '<s3-object-key>.bin'
  },

I believe the problem is that when a deferred promise is created it does not handle rejections. It is only inserted into a list.
https://github.com/tus/tus-node-server/blob/81eb03a82658b2e5e97abdb05c26418192320724/packages/s3-store/src/index.ts#L421
At the end promises are aggregated with Promise.all and returned to the caller where an rejection handler is eventually added.
https://github.com/tus/tus-node-server/blob/81eb03a82658b2e5e97abdb05c26418192320724/packages/s3-store/src/index.ts#L440
https://github.com/tus/tus-node-server/blob/81eb03a82658b2e5e97abdb05c26418192320724/packages/server/src/server.ts#L188

During this period if any of the promises in a list are rejected, the rejection is not handled.

Steps to reproduce

Use S3 server that sometimes throws errors (e.g. Scaleway Object Storage) or manually throw error. I modified uploadPart(metadata, readStream, partNumber) to randomly reject. Add provided snippet at the top of this function. This will result in server crash.

const errorProbability = Math.random()
if (errorProbability < 0.2) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new Error('test'));
    }, 1000);
  })
} 

Expected behavior

Server should not crash. Failed part upload should end the current upload with HTTP 500 Internal Server Error status code.

Observation

Promises (rejections) returned by acquiredPermit?.release() and permit?.release() are also not handled and would cause server to crash.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in packages/s3-store/src/index.ts at the deferred promises around lines 421 and 440, then inspect packages/server/src/server.ts around line 188 to trace rejection handling. Reproduce an uploadPart or uploadIncompletePart rejection, including permit.release() failures, and verify that the server remains running while the current upload returns HTTP 500.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, typescript
Domain
backend, cloud
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.