-
Notifications
You must be signed in to change notification settings - Fork 202
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
@tus/s3-store: finalize incomplete parts #502
Conversation
b0fc185
to
67fe45a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
67fe45a
to
4c609e7
Compare
4c609e7
to
6383aa7
Compare
Will need to take a closer look at this patch after the recent changes in For an unknown reason, with those changes, calling tus-node-server:stores:s3store [tus-test/test-0.7704705088837052] failed to finish upload NotImplemented: A header you provided implies functionality that is not implemented
at throwDefaultError (<path>/node_modules/.pnpm/@[email protected]/node_modules/@smithy/smithy-client/dist-cjs/default-error-handler.js:8:22)
at <path>/node_modules/.pnpm/@[email protected]/node_modules/@smithy/smithy-client/dist-cjs/default-error-handler.js:18:39
at de_UploadPartCommandError (<path>//node_modules/.pnpm/@[email protected]/node_modules/@aws-sdk/client-s3/dist-cjs/protocols/Aws_restXml.js:5971:12)
at processTicksAndRejections (node:internal/process/task_queues:95:5)
at async <path>/node_modules/.pnpm/@[email protected]/node_modules/@smithy/middleware-serde/dist-cjs/deserializerMiddleware.js:7:24
at async <path>/node_modules/.pnpm/@[email protected]/node_modules/@aws-sdk/middleware-signing/dist-cjs/awsAuthMiddleware.js:14:20
at async <path>/node_modules/.pnpm/@[email protected]/node_modules/@smithy/middleware-retry/dist-cjs/retryMiddleware.js:27:46
at async <path>/node_modules/.pnpm/@[email protected]/node_modules/@aws-sdk/middleware-flexible-checksums/dist-cjs/flexibleChecksumsMiddleware.js:63:20
at async <path>/node_modules/.pnpm/@[email protected]/node_modules/@aws-sdk/middleware-sdk-s3/dist-cjs/region-redirect-endpoint-middleware.js:14:24
at async <path>/node_modules/.pnpm/@[email protected]/node_modules/@aws-sdk/middleware-sdk-s3/dist-cjs/region-redirect-middleware.js:9:20 {
'$fault': 'client',
'$metadata': {
httpStatusCode: 501,
requestId: '<omitted>',
extendedRequestId: '<omitted>',
cfId: undefined,
attempts: 1,
totalRetryDelay: 0
},
Code: 'NotImplemented',
Header: 'Transfer-Encoding',
RequestId: '<omitted>',
HostId: '<omitted>'
} |
Not sure what is the problem with the error you get. Regarding the failed tests (https://github.com/tus/tus-node-server/pull/502/checks?check_run_id=18095601809) you need to fix test I ran your tests and everything seems to work. |
@@ -504,7 +504,13 @@ export class S3Store extends DataStore { | |||
|
|||
if (metadata.file.size === newOffset) { | |||
try { | |||
// If no parts exist yet, then the incomplete part needs to be completed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// If no parts exist yet, then the incomplete part needs to be completed | |
// If there is any incomplete part it needs to be completed |
// The multipart upload will now be completed | ||
assert.equal(finishMultipartUpload.called, true) | ||
}) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prepared another test where we test if it works when there is a one complete part and one incomplete part.
it('completes an incomplete part when deferred length becomes resolved', async function () {
const store = this.datastore
const firstPartSize = 8 * 1024 * 1024 // 8MB
const secondPartSize = 2 * 1024 * 1024 // 2MB
const uploadPart = sinon.spy(store, 'uploadPart')
const getIncompletePart = sinon.spy(store, 'getIncompletePart')
const uploadIncompletePart = sinon.spy(store, 'uploadIncompletePart')
const finishMultipartUpload = sinon.spy(store, 'finishMultipartUpload')
sinon.stub(store, 'calcOptimalPartSize').callsFake(() => firstPartSize)
const upload = new Upload({
id: 'deferred-incomplete-part-test-' + Uid.rand(),
// Deferred length
size: undefined,
offset: 0,
})
await store.create(upload)
// Upload a single chunk large enough to create a part
{
upload.offset = await store.write(
Readable.from(Buffer.alloc(firstPartSize)),
upload.id,
upload.offset
)
assert.equal(uploadPart.called, true)
assert.equal(uploadIncompletePart.called, false)
}
// Upload a single chunk small enough to create an incomplete part
{
uploadPart.resetHistory()
uploadIncompletePart.resetHistory()
upload.offset = await store.write(
Readable.from(Buffer.alloc(secondPartSize)),
upload.id,
upload.offset
)
assert.equal(uploadIncompletePart.called, true)
assert.equal(uploadPart.called, false)
}
// Simulate the completion PATCH of a deferred length multipart upload
{
getIncompletePart.resetHistory()
uploadIncompletePart.resetHistory()
// Resolve the deferred length
await store.declareUploadLength(upload.id, firstPartSize + secondPartSize)
// Notify the store to complete the multipart upload (empty payload)
await store.write(Readable.from(Buffer.alloc(0)), upload.id, upload.offset)
// Final write needs to check for incomplete part
assert.equal(getIncompletePart.called, true)
// The incomplete part will now be completed
assert.equal(uploadPart.called, true)
// The multipart upload will now be completed
assert.equal(finishMultipartUpload.called, true)
}
})
@atticoos still interested in getting this over the finish line? |
I welcome contributions if anyone wants to take over |
Yes |
Fixes #501
When an upload completes with only a single incomplete part having been uploaded,
uploadPart
has never run to establish the first part.As a result,
parts
are empty, andinalizeMultipartUpload
fails with a500
.Fix
Wasn't sure of the bets way to accomplish this, since the final step is an empty
PATCH
with justUpload-Length
header, so the mainprocessUpload
routine that normally callsuploadPart
anduploadIncompletePart
will not run a final time -- so I performed the finaluploadPart
as part of thewrite
procedure, ensuring the firstpart
will always exist forfinishMultipartUpload
to run.