{"record":{"id":"35f709869c6398f6","repo":"apache/iceberg","slug":"interrupted-when-waiting-for-deletions-to-complete","errorCode":null,"errorMessage":"Interrupted when waiting for deletions to complete","messagePattern":"Interrupted when waiting for deletions to complete","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"warning","filePath":"aws/src/main/java/org/apache/iceberg/aws/s3/S3FileIO.java","lineNumber":264,"sourceCode":"        Future<List<String>> deletionTask =\n            executorService()\n                .submit(() -> deleteBatch(clientForStoragePath(\"s3://\" + bucket), bucket, keys));\n        deletionTasks.add(deletionTask);\n      }\n\n      int totalFailedDeletions = 0;\n\n      for (Future<List<String>> deletionTask : deletionTasks) {\n        try {\n          List<String> failedDeletions = deletionTask.get();\n          failedDeletions.forEach(path -> LOG.warn(\"Failed to delete object at path {}\", path));\n          totalFailedDeletions += failedDeletions.size();\n        } catch (ExecutionException e) {\n          LOG.warn(\"Caught unexpected exception during batch deletion: \", e.getCause());\n        } catch (InterruptedException e) {\n          Thread.currentThread().interrupt();\n          deletionTasks.stream().filter(task -> !task.isDone()).forEach(task -> task.cancel(true));\n          throw new RuntimeException(\"Interrupted when waiting for deletions to complete\", e);\n        }\n      }\n\n      if (totalFailedDeletions > 0) {\n        throw new BulkDeletionFailureException(totalFailedDeletions);\n      }\n    }\n  }\n\n  private void tagFileToDelete(PrefixedS3Client client, String path, Set<Tag> deleteTags)\n      throws S3Exception {\n    S3URI location = new S3URI(path, client.s3FileIOProperties().bucketToAccessPointMapping());\n    String bucket = location.bucket();\n    String objectKey = location.key();\n    GetObjectTaggingRequest getObjectTaggingRequest =\n        GetObjectTaggingRequest.builder().bucket(bucket).key(objectKey).build();\n    GetObjectTaggingResponse getObjectTaggingResponse =\n        client.s3().getObjectTagging(getObjectTaggingRequest);","sourceCodeStart":246,"sourceCodeEnd":282,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/aws/src/main/java/org/apache/iceberg/aws/s3/S3FileIO.java#L246-L282","documentation":"RuntimeException thrown by S3FileIO.deleteFiles when the thread waiting on the batch deletion tasks is interrupted. The method re-interrupts the current thread, cancels all outstanding deletion tasks, and wraps the InterruptedException in a RuntimeException. It signals the caller's shutdown/interrupt rather than an S3 problem.","triggerScenarios":"deleteFiles (also invoked via deletePrefix) waiting on futures of S3 batch deletion tasks when the executing thread is interrupted — e.g. task cancellation in Spark/Flink, executor shutdown, or Thread.interrupt() during cleanup.","commonSituations":"Cancelling a Spark job whose ExpireSnapshots/deleteFiles cleanup is in flight; shutting down an executor while bulk deletes are running; a query killed by a timeout mechanism that interrupts threads; Flink task manager failover interrupting cleanup threads.","solutions":["Avoid interrupting the thread during deleteFiles; ensure graceful shutdown lets cleanup finish or accepts partial deletion (S3 deletes are per-object, retryable).","Re-run the deletion — file deletes are idempotent for already-deleted keys; re-invoke deleteFiles after the interruption.","If interruption is expected (job cancellation), catch RuntimeException at the call site and check Thread.currentThread().isInterrupted() to restore/record the interrupt state.","Move large bulk deletions to a dedicated lifecycle/expiration job less likely to be interrupted."],"exampleFix":"// before\nio.deleteFiles(filesToDelete); // running inside a cancellable Spark task, interrupted on job kill\n// after\ntry {\n  io.deleteFiles(filesToDelete);\n} catch (RuntimeException e) {\n  if (Thread.currentThread().isInterrupted()) {\n    LOG.warn(\"Deletion interrupted, will retry on restart\", e);\n  } else {\n    throw e;\n  }\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  io.deleteFiles(paths);\n} catch (RuntimeException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"Interrupted when waiting for deletions\")) {\n    Thread.currentThread().interrupt(); // preserve interrupt status\n    LOG.warn(\"Bulk deletion interrupted; deletes are idempotent, retry later\");\n  } else {\n    throw e;\n  }\n}","preventionTips":["Allow graceful shutdown so deletion cleanup can finish","Treat file deletions as idempotent and safe to re-run","Run bulk deletes in dedicated maintenance jobs less prone to interruption","Avoid Thread.interrupt on threads performing FileIO cleanup"],"tags":["aws","s3","deletion","interruption","concurrency"],"backgroundTag":"thread-interrupted","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}