Skip to content

putObject() never resolves or rejects if the input stream gets destroyed on upload #1479

@yucao2521

Description

@yucao2521

Description

When putObject() is called with a Readable stream that gets destroyed afterwards, the return promise never resolves or rejects.

Reproduction

Node.js version: v22.22.2
minio-js version: 8.0.7

import { Client } from 'minio';
import { PassThrough } from 'node:stream';

const s = new PassThrough().on('error', () => {});

const client = new Minio.Client({
  endPoint: 'localhost',
  port: 9000,
  useSSL: false,
  accessKey: 'minioadmin',
  secretKey: 'minioadmin',
});

const promise = client.putObject('my-bucket', 'my-file', s);
s.destroy(new Error('stream error'));

try {
  await promise;
} catch (error) {
  console.log('error caught:', error);
}

Output:

> node test.js
Warning: Detected unsettled top-level await at file:///..../test.js
  await promise;
  ^

Troubleshooting

Here in uploadStream, it uses body.pipe(chunkier) which returns chunkier, so the error listener is set on chunkier twice. When body gets destroyed, chunkier is left open and the Promise doesn't resolve or reject.

      new Promise((resolve, reject) => {
        body.pipe(chunkier).on('error', reject)
        chunkier.on('end', resolve).on('error', reject)
      }),

Suggested Fix

Use stream.pipeline() instead which provides automatic error forwarding and cleanup when streams finish or encounter errors.

      new Promise((resolve, reject) => {
        stream.pipeline(body, chunkier)
        chunkier.on('end', resolve).on('error', reject)
      }),

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions