使用JGit批量克隆Gerrit仓库时触发Too many open files错误
批量克隆Gerrit仓库时出现"Too many open files"错误
使用JGit循环克隆Gerrit上的2000+代码仓库,克隆到约第1000个时失败,报错创建锁文件失败,提示Too many open files。
克隆实现代码
override fun downloadProject(moduleName: String): Path { val tempDirectory = Files.createTempDirectory(moduleName) val cloneCommand = Git.cloneRepository() moduleInfoDao.getModuleByName(moduleName).codeRepository?.let { cloneCommand.setURI(makeSshUri(it)) } ?: throw ModuleWithoutRepositoryException() cloneCommand.setTransportConfigCallback { transport -> val sshTransport = transport as SshTransport sshTransport.sshSessionFactory = sshSessionFactory } cloneCommand.setDirectory(tempDirectory.toFile()) cloneCommand.setBare(false) val cloneResult = cloneCommand.call() cloneResult.close() cloneCommand?.repository?.close() return tempDirectory }
异常堆栈信息
> 2022-09-24 19:26:15.941 [ool-request-processor-thread-1] ERROR Creating lock file /var/folders/6_/h619j3tx5fscmn4q2wr0vqth0000gp/T/module-agents-offline15007549319743888409/.git/HEAD.lock failed - <o.e.j.internal.storage.file.LockFile> - org.eclipse.jgit.internal.storage.file.LockFile.lock(LockFile.java:144) java.nio.file.FileSystemException: /var/folders/6_/h619j3tx5fscmn4q2wr0vqth0000gp/T/module-agents-offline15007549319743888409/.git/HEAD.lock: Too many open files at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:100) at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:106) at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111) at java.base/sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:218) at java.base/java.nio.file.Files.newByteChannel(Files.java:375) at java.base/java.nio.file.Files.createFile(Files.java:652) at org.eclipse.jgit.util.FS_POSIX.createNewFileAtomic(FS_POSIX.java:428) at org.eclipse.jgit.internal.storage.file.LockFile.lock(LockFile.java:142) at org.eclipse.jgit.internal.storage.file.RefDirectoryUpdate.tryLock(RefDirectoryUpdate.java:56) at org.eclipse.jgit.lib.RefUpdate.link(RefUpdate.java:678) at org.eclipse.jgit.internal.storage.file.FileRepository.create(FileRepository.java:246) at org.eclipse.jgit.api.InitCommand.call(InitCommand.java:103) at org.eclipse.jgit.api.CloneCommand.init(CloneCommand.java:275) at org.eclipse.jgit.api.CloneCommand.call(CloneCommand.java:173) at ru.myorganization.payin.modules.quality.service.GitDownloaderService.downloadProject(GitDownloaderService.kt:48) at ru.myorganization.payin.modules.quality.service.ProjectDownloadingAbstractnessService.getAbstractnessFactor(ProjectDownloadingAbstractnessService.kt:15) at ru.myorganization.payin.modules.quality.service.StableAbstractionsPrincipleService.getWarnings(StableAbstractionsPrincipleService.kt:25) at ru.myorganization.payin.modules.quality.service.StableAbstractionsPrincipleService$$FastClassBySpringCGLIB$$787d1f0a.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) at org.springframework.cache.interceptor.CacheInterceptor.lambda$invoke$0(CacheInterceptor.java:54) at org.springframework.cache.interceptor.CacheAspectSupport.invokeOperation(CacheAspectSupport.java:366) at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:421) at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:345) at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:64) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) at ru.myorganization.payin.modules.quality.service.StableAbstractionsPrincipleService$$EnhancerBySpringCGLIB$$42c48c58.getWarnings(<generated>) at ru.myorganization.payin.modules.quality.service.WarningsCompositeService.getWarnings(WarningsCompositeService.kt:19) at ru.myorganization.payin.modules.quality.controllers.scheduler.FindModuleWarningsScheduler.setWarnings(FindModuleWarningsScheduler.kt:43) at ru.myorganization.payin.modules.quality.controllers.scheduler.FindModuleWarningsScheduler.analyzeAllModules(FindModuleWarningsScheduler.kt:25) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:564) at org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:84) at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515) at java.base/java.util.concurrent.FutureTask.runAndReset$$$capture(FutureTask.java:305) at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java) at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) at java.base/java.lang.Thread.run(Thread.java:832)
问题原因及解决办法
原因分析
Too many open files错误是因为进程打开的文件句柄数超过系统限制。现有代码虽调用了关闭方法,但存在资源释放不彻底的情况:
- JGit克隆过程中会打开多个底层文件流,单纯关闭
Git实例和Repository可能遗漏部分句柄 - 循环克隆时资源释放不及时,导致句柄持续累积
解决步骤
用
use函数确保资源自动释放
Kotlin的use函数可自动管理资源生命周期,确保所有关联文件句柄被关闭:override fun downloadProject(moduleName: String): Path { val tempDirectory = Files.createTempDirectory(moduleName) val cloneCommand = Git.cloneRepository() moduleInfoDao.getModuleByName(moduleName).codeRepository?.let { cloneCommand.setURI(makeSshUri(it)) } ?: throw ModuleWithoutRepositoryException() cloneCommand.setTransportConfigCallback { transport -> val sshTransport = transport as SshTransport sshTransport.sshSessionFactory = sshSessionFactory } cloneCommand.setDirectory(tempDirectory.toFile()) cloneCommand.setBare(false) // use块结束后自动关闭Git实例及关联资源 cloneCommand.call().use { /* 克隆完成后的操作 */ } cloneCommand.repository?.close() return tempDirectory }及时清理临时目录(可选)
如果克隆完成后不需要保留临时目录,可在方法末尾添加清理逻辑,释放目录关联的句柄:// 仅当后续无需使用临时目录时执行 // Files.walk(tempDirectory).sorted(Comparator.reverseOrder()).forEach(Files::delete)调高系统文件句柄限制
若代码优化后仍报错,可临时调高进程的文件句柄限制:- Linux临时生效:执行
ulimit -n 4096 - Linux永久生效:编辑
/etc/security/limits.conf,添加:soft nofile 4096 hard nofile 8192
- Linux临时生效:执行
限制并发克隆数
如果是并发克隆导致句柄激增,控制并发线程数,避免同时打开过多资源。
内容的提问来源于stack exchange,提问作者алексей лашнев
相关产品推荐
相关产品推荐

