You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot归档应用使用Transfer Manager后无法自动shutdown问题排查

问题:Spring Boot非Web应用改用S3 Transfer Manager后无法自动关闭

我基于Spring Boot开发了一个归档应用,配置spring.main.web-application-type: none,原本启动后会从数据库拉取数据,通过PutObject上传至S3,之后自动关闭应用,日志显示Hikari连接池、JPA实体管理器工厂正常关闭,进程退出码为0。

近期为优化1-2百万级文件的S3上传性能,改用Transfer Manager。现在应用执行流程为:从数据库取数据→生成文件→通过Transfer Manager批量上传至S3,流程执行完成后日志停留在“Archiver process completed with status SUCCESS”,应用无法自动关闭。

排查后怀疑是生成文件所用的PrintWriter未正常关闭,但已使用try-with-resources语法,也尝试过手动调用close()方法,排除该原因。

文件生成代码

public void storeFile(KeyEntity keyEntity, List<JsonObject> jsonObjects, String archiveDate) {

        String fileName= getFileName(archiveDate,keyEntity);
        String path = ArchiveProperties.getFileUploadPath();
        Path dir= Paths.get(path);

        if(!Files.exists(dir)){
            try {
                Files.createDirectories(dir);
            } catch (IOException e) {
                log.error("Exception occurred while creating directory {}",ArchiveProperties.getFileUploadPath());
                throw new RuntimeException(e);
            }
        }
        Path file = Paths.get(path,fileName);
        try(PrintWriter printWriter = new PrintWriter(new FileWriter(file.toFile()))) {
            printWriter.print(jsonObjects);
        } catch (IOException e) {
            log.error("Exception occurred while writing into directory {} for filename {}", ArchiveProperties.getFileUploadPath(), fileName);
            throw new RuntimeException(e);
        }
    }

Transfer Manager批量上传代码

public Boolean bulkUpload(String path, S3Properties s3Properties,String filePrefix){

        log.info("S3ObjectStore:bulkUpload :: Initialing bulk upload for path {}",path);

        AmazonS3Client s3 = S3Configuration.getS3Client(provider,s3Properties);
        TransferManager transferManager = TransferManagerBuilder.standard().withS3Client(s3).build();
        try {

            MultipleFileUpload multipleFileUpload = transferManager.uploadDirectory(s3Properties.getBucket(),filePrefix,new File(path),false);
            showTransferProgress(multipleFileUpload);
            multipleFileUpload.waitForCompletion();

        }
        catch ( Exception e) {
            log.error("S3ObjectStore:bulkUpload :: Exception occurred while uploading directory to s3",e);
            return Boolean.FALSE;
        }finally {
            transferManager.shutdownNow();
          
        }
        return Boolean.TRUE;
    }

AmazonS3Client创建代码

public static AmazonS3Client getS3Client(final IdaS3ClientKeysProvider provider, final S3Properties s3Properties){

        log.info("Creating AmazonS3Client for bulk upload ..............");
        IdaS3ClientKeysProvider idaS3ClientKeysProvider = provider;
        //Set up the connection configuration
        ClientConfiguration config = new ClientConfiguration().withRetryPolicy(ClientConfiguration.DEFAULT_RETRY_POLICY);
        //DEFAULT_RETRY_POLICY=3 times retry for failure files
        config.setProtocol(Protocol.HTTPS);
        config.setSignerOverride("S3SignerType");
        //Set up the AWS credentials
        BasicAWSCredentials credentials = new BasicAWSCredentials(idaS3ClientKeysProvider.getS3Keys().getS3AccessKey(),idaS3ClientKeysProvider.getS3Keys().getS3SecretKey());
        //Set up AWS Client
        AmazonS3Client client = new AmazonS3Client(credentials,config);
        client.setS3ClientOptions(S3ClientOptions.builder().setPathStyleAccess(true).build());
        client.setEndpoint(s3Properties.getDataplaneEndpoint());
        log.info("Inside AmazonS3Client :: access key :{}, secret key :{}",provider.getS3Keys().getS3AccessKey(),provider.getS3Keys().getS3SecretKey());
        return client;
    }

解决方案

1. 显式关闭AmazonS3Client

TransferManager默认不会关闭传入的S3Client,其内部后台线程可能阻止JVM退出。在bulkUpload方法的finally块中添加S3Client关闭逻辑:

finally {
    transferManager.shutdownNow();
    s3.shutdown(); // 显式关闭S3Client,释放后台线程资源
}

2. 自定义TransferManager线程池为守护线程

TransferManager默认线程池中的非守护线程会维持JVM运行。可以自定义线程池,将线程设置为守护线程:

// 在bulkUpload方法中创建TransferManager时修改
ExecutorService executor = Executors.newFixedThreadPool(10, r -> {
    Thread t = new Thread(r);
    t.setDaemon(true); // 守护线程会随JVM退出自动终止
    return t;
});

TransferManager transferManager = TransferManagerBuilder.standard()
        .withS3Client(s3)
        .withExecutorFactory(() -> executor)
        .build();

3. 主动触发Spring上下文关闭

如果应用通过CommandLineRunner或ApplicationRunner执行归档逻辑,任务完成后主动触发上下文关闭,确保所有资源释放:

@Component
public class ArchiveRunner implements CommandLineRunner {

    @Autowired
    private ApplicationContext context;
    @Autowired
    private ArchiveService archiveService;

    @Override
    public void run(String... args) throws Exception {
        boolean success = archiveService.executeArchive();
        int exitCode = success ? 0 : 1;
        // 主动触发上下文关闭并退出进程
        SpringApplication.exit(context, () -> exitCode);
        System.exit(exitCode);
    }
}

4. 排查残留线程(可选)

若以上方法无效,使用jstack命令导出线程栈,定位未终止的线程:

jstack <进程ID> > thread_dump.txt

分析线程栈中是否存在AWS相关的非守护线程,进一步针对性处理。


内容的提问来源于stack exchange,提问作者Shaggy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:44:54