From 23c9534568b689b8722fb9cfa91126a0b8758677 Mon Sep 17 00:00:00 2001 From: TippyFlits Date: Thu, 9 Apr 2026 16:45:31 +0100 Subject: [PATCH 1/5] fix(synapse-core): retry upload session creation on transient network failures The POST /pdp/piece/uploads request that creates an upload session is a lightweight stateless call that should respond in under a second. When it encounters a transient network error (fetch failed, connection reset), it currently waits the full 5-minute MAX_RETRY_TIME before failing the entire upload. Replace the 5-minute single-attempt timeout with a 30-second timeout and 2 retries. Each retry creates a fresh session UUID; abandoned sessions are cleaned up by Curio automatically. The streaming upload and finalize steps are unchanged. --- packages/synapse-core/src/sp/upload-streaming.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/synapse-core/src/sp/upload-streaming.ts b/packages/synapse-core/src/sp/upload-streaming.ts index 859d31912..3c9c677dd 100644 --- a/packages/synapse-core/src/sp/upload-streaming.ts +++ b/packages/synapse-core/src/sp/upload-streaming.ts @@ -47,8 +47,14 @@ export async function uploadPieceStreaming( ): Promise { // Create upload session (POST /pdp/piece/uploads) const createResponse = await request.post(new URL('pdp/piece/uploads', options.serviceURL), { - timeout: RETRY_CONSTANTS.MAX_RETRY_TIME, + timeout: 30_000, signal: options.signal, + retry: { + retries: 2, + methods: ['post'], + minTimeout: 2_000, + shouldRetry: () => true, + }, }) if (createResponse.error) { From ef0465882cf63d3860639cbfb0a8ed001a7cfd5f Mon Sep 17 00:00:00 2001 From: TippyFlits Date: Thu, 9 Apr 2026 17:27:28 +0100 Subject: [PATCH 2/5] fix(synapse-core): only retry network errors, not HTTP errors --- packages/synapse-core/src/sp/upload-streaming.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/synapse-core/src/sp/upload-streaming.ts b/packages/synapse-core/src/sp/upload-streaming.ts index 3c9c677dd..e48b63e88 100644 --- a/packages/synapse-core/src/sp/upload-streaming.ts +++ b/packages/synapse-core/src/sp/upload-streaming.ts @@ -53,7 +53,7 @@ export async function uploadPieceStreaming( retries: 2, methods: ['post'], minTimeout: 2_000, - shouldRetry: () => true, + shouldRetry: (ctx) => !HttpError.is(ctx.error), }, }) From 88ba68574eef3e603cacd97cb122a9218b819f23 Mon Sep 17 00:00:00 2001 From: TippyFlits Date: Thu, 9 Apr 2026 17:38:24 +0100 Subject: [PATCH 3/5] test: increase timeout for batch error test to account for upload session retries --- packages/synapse-sdk/src/test/storage.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/synapse-sdk/src/test/storage.test.ts b/packages/synapse-sdk/src/test/storage.test.ts index d0490eca5..7677e6411 100644 --- a/packages/synapse-sdk/src/test/storage.test.ts +++ b/packages/synapse-sdk/src/test/storage.test.ts @@ -893,7 +893,8 @@ describe('StorageService', () => { }) describe('upload', () => { - it('should handle errors in batch processing gracefully', async () => { + it('should handle errors in batch processing gracefully', async function () { + this.timeout(30_000) server.use( Mocks.JSONRPC({ ...Mocks.presets.basic, From 81c9fcc5f40343c9f3132713da6ab534fd1a3faf Mon Sep 17 00:00:00 2001 From: TippyFlits Date: Thu, 9 Apr 2026 17:52:39 +0100 Subject: [PATCH 4/5] test: increase timeouts for tests that trigger upload session retries --- packages/synapse-sdk/src/test/storage.test.ts | 3 ++- packages/synapse-sdk/src/test/synapse.test.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/synapse-sdk/src/test/storage.test.ts b/packages/synapse-sdk/src/test/storage.test.ts index 7677e6411..c2d301a2b 100644 --- a/packages/synapse-sdk/src/test/storage.test.ts +++ b/packages/synapse-sdk/src/test/storage.test.ts @@ -1129,7 +1129,8 @@ describe('StorageService', () => { } }) - it('should handle upload piece failure', async () => { + it('should handle upload piece failure', async function () { + this.timeout(30_000) const testData = new Uint8Array(127).fill(42) const testPieceCID = Piece.calculate(testData).toString() const mockUuid = '12345678-90ab-cdef-1234-567890abcdef' diff --git a/packages/synapse-sdk/src/test/synapse.test.ts b/packages/synapse-sdk/src/test/synapse.test.ts index 10d23bd14..8014c9a07 100644 --- a/packages/synapse-sdk/src/test/synapse.test.ts +++ b/packages/synapse-sdk/src/test/synapse.test.ts @@ -826,7 +826,8 @@ describe('Synapse', () => { assert.equal(result.size, 1024) }) - it('fails when primary store fails', async () => { + it('fails when primary store fails', async function () { + this.timeout(30_000) const data = new Uint8Array(1024) const pdpOptions = { baseUrl: Mocks.PROVIDERS.provider1.products[0].offering.serviceURL, From 3122f8d73d8ad2c239837ef6f561996a746d514e Mon Sep 17 00:00:00 2001 From: TippyFlits Date: Thu, 9 Apr 2026 17:58:45 +0100 Subject: [PATCH 5/5] test: use HTTP 500 instead of network errors for upload session failure tests Tests that simulate upload session creation failures now return HTTP 500 instead of HttpResponse.error() (network error). This correctly tests server-side failures without triggering the new network error retry logic. --- packages/synapse-sdk/src/test/storage.test.ts | 13 +++++-------- packages/synapse-sdk/src/test/synapse.test.ts | 5 ++--- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/packages/synapse-sdk/src/test/storage.test.ts b/packages/synapse-sdk/src/test/storage.test.ts index c2d301a2b..08ea680b6 100644 --- a/packages/synapse-sdk/src/test/storage.test.ts +++ b/packages/synapse-sdk/src/test/storage.test.ts @@ -893,8 +893,7 @@ describe('StorageService', () => { }) describe('upload', () => { - it('should handle errors in batch processing gracefully', async function () { - this.timeout(30_000) + it('should handle errors in batch processing gracefully', async () => { server.use( Mocks.JSONRPC({ ...Mocks.presets.basic, @@ -906,7 +905,7 @@ describe('StorageService', () => { http.post, { pieceCid: string }>( 'https://pdp.example.com/pdp/piece/uploads', async () => { - return HttpResponse.error() + return HttpResponse.text('Internal Server Error', { status: 500 }) } ) ) @@ -1129,8 +1128,7 @@ describe('StorageService', () => { } }) - it('should handle upload piece failure', async function () { - this.timeout(30_000) + it('should handle upload piece failure', async () => { const testData = new Uint8Array(127).fill(42) const testPieceCID = Piece.calculate(testData).toString() const mockUuid = '12345678-90ab-cdef-1234-567890abcdef' @@ -1139,9 +1137,8 @@ describe('StorageService', () => { ...Mocks.presets.basic, }), Mocks.PING(), - Mocks.pdp.postPieceHandler(testPieceCID, mockUuid, pdpOptions), - http.put('https://pdp.example.com/pdp/piece/upload/:uuid', async () => { - return HttpResponse.error() + http.post('https://pdp.example.com/pdp/piece/uploads', async () => { + return HttpResponse.text('Internal Server Error', { status: 500 }) }) ) const synapse = new Synapse({ client, source: null }) diff --git a/packages/synapse-sdk/src/test/synapse.test.ts b/packages/synapse-sdk/src/test/synapse.test.ts index 8014c9a07..6fc9fb6b1 100644 --- a/packages/synapse-sdk/src/test/synapse.test.ts +++ b/packages/synapse-sdk/src/test/synapse.test.ts @@ -826,8 +826,7 @@ describe('Synapse', () => { assert.equal(result.size, 1024) }) - it('fails when primary store fails', async function () { - this.timeout(30_000) + it('fails when primary store fails', async () => { const data = new Uint8Array(1024) const pdpOptions = { baseUrl: Mocks.PROVIDERS.provider1.products[0].offering.serviceURL, @@ -835,7 +834,7 @@ describe('Synapse', () => { // Primary SP rejects upload server.use( http.post(`${pdpOptions.baseUrl}/pdp/piece/uploads`, async () => { - return HttpResponse.error() + return HttpResponse.text('Internal Server Error', { status: 500 }) }) ) try {