SVNKit requires write access to repository files when using file:// access

Hey everybody, I’d like to discuss about https://issues.tmatesoft.com/issue/SVNKIT-418 however I could not log in to YouTrack because I have never had an account there and support has moved to here, so I created an account here to continue discussing about that problem.

Is there any way to use just read-only access? Currently, when a remote repository is mirrored via svnsync on Windows environments our application (which has just read-only access to the file system) faces a java.nio.channels.NonWritableChannelException.

Based on what we’ve found out thus far there two possible causes:

  1. SVNKit seems to detect that several threads (svnsync) are accessing the SVN repository and ends up trying to write a lock to the repository. The write process fails because our application has read-only file permission to the local disk repository.
  2. Another possible reason for this problem is the existence of a zero-byte file called rep-cache.db-journal

The workarounds we’ve found are:

  1. Add write permission to the rep-cache.db file in the repository root directory
  2. Remove the rep-cache.db-journal file

Here’s the full stack trace requested in the original ticket:

2022-02-16 08:57:10,546 WARN  [InitPing2 SVNRepoName ] fisheye SvnRepositoryTester-getServerRootURL - Unable to get info for the repository root for SVNRepoName
com.cenqua.fisheye.rep.RepositoryClientException: java.nio.channels.NonWritableChannelException
	at com.cenqua.fisheye.svn.SvnThrottledClient.executeNoThrottle(SvnThrottledClient.java:192) [fisheye.jar:?]
	at com.cenqua.fisheye.svn.SvnThrottledClient.execute(SvnThrottledClient.java:161) [fisheye.jar:?]
	at com.cenqua.fisheye.svn.SvnThrottledClient.info(SvnThrottledClient.java:113) [fisheye.jar:?]
	at com.cenqua.fisheye.svn.SvnRepositoryTester.getServerRootURL(SvnRepositoryTester.java:91) [fisheye.jar:?]
	at com.cenqua.fisheye.svn.SvnRepositoryTester.checkRepoSettings(SvnRepositoryTester.java:72) [fisheye.jar:?]
	at com.cenqua.fisheye.svn.SvnRepositoryTester.testConnection(SvnRepositoryTester.java:65) [fisheye.jar:?]
	at com.atlassian.fisheye.svn.Svn2Scanner.validateRepository(Svn2Scanner.java:133) [fisheye.jar:?]
	at com.atlassian.fisheye.svn.Svn2Scanner.doSlurpTransaction(Svn2Scanner.java:186) [fisheye.jar:?]
	at com.cenqua.fisheye.rep.BaseRepositoryScanner.ping(BaseRepositoryScanner.java:73) [fisheye.jar:?]
	at com.cenqua.fisheye.rep.BaseRepositoryEngine.doSlurp(BaseRepositoryEngine.java:85) [fisheye.jar:?]
	at com.cenqua.fisheye.rep.RepositoryEngine.slurp(RepositoryEngine.java:419) [fisheye.jar:?]
	at com.cenqua.fisheye.rep.ping.IndexingPingRequest.doRequest(IndexingPingRequest.java:28) [fisheye.jar:?]
	at com.cenqua.fisheye.rep.ping.IncrementalPingRequest.doRequest(IncrementalPingRequest.java:30) [fisheye.jar:?]
	at com.cenqua.fisheye.rep.ping.PingRequest$1.run(PingRequest.java:55) [fisheye.jar:?]
	at com.cenqua.fisheye.util.NamedExecution.run(NamedExecution.java:27) [fisheye.jar:?]
	at com.cenqua.fisheye.rep.ping.PingRequest.process(PingRequest.java:52) [fisheye.jar:?]
	at com.cenqua.fisheye.rep.RepositoryHandle.processPingRequests(RepositoryHandle.java:211) [fisheye.jar:?]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) [?:1.8.0_261]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) [?:1.8.0_261]
	at java.lang.Thread.run(Unknown Source) [?:1.8.0_261]
Caused by: java.nio.channels.NonWritableChannelException
	at sun.nio.ch.FileChannelImpl.tryLock(Unknown Source) [?:1.8.0_261]
	at org.tmatesoft.sqljet.core.internal.fs.SqlJetFileLockManager$1.createLock(SqlJetFileLockManager.java:57) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.internal.fs.SqlJetFileLockManager.createLock(SqlJetFileLockManager.java:81) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.internal.fs.SqlJetFileLockManager.tryLock(SqlJetFileLockManager.java:102) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.internal.fs.SqlJetFile.checkReservedLock(SqlJetFile.java:714) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.internal.pager.SqlJetPager.hasHotJournal(SqlJetPager.java:2484) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.internal.pager.SqlJetPager.sharedLock(SqlJetPager.java:1251) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.internal.pager.SqlJetPager.acquirePage(SqlJetPager.java:1020) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.internal.btree.SqlJetBtree.getMeta(SqlJetBtree.java:2196) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.internal.table.SqlJetOptions.readSchemaCookie(SqlJetOptions.java:213) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.internal.table.SqlJetOptions.<init>(SqlJetOptions.java:93) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.table.engine.SqlJetEngine$2.runSynchronized(SqlJetEngine.java:281) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.table.engine.SqlJetEngine.runSynchronized(SqlJetEngine.java:217) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.table.engine.SqlJetEngine.readSchema(SqlJetEngine.java:276) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.table.engine.SqlJetEngine.getOptions(SqlJetEngine.java:299) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.svn.core.internal.io.fs.repcache.FSRepresentationCacheManager$1.runWithLock(FSRepresentationCacheManager.java:91) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.sqljet.core.table.SqlJetDb$1.runSynchronized(SqlJetDb.java:172) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.table.engine.SqlJetEngine.runSynchronized(SqlJetEngine.java:217) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.sqljet.core.table.SqlJetDb.runWithLock(SqlJetDb.java:170) [sqljet-1.1.12.jar:?]
	at org.tmatesoft.svn.core.internal.io.fs.repcache.FSRepresentationCacheManager.checkFormat(FSRepresentationCacheManager.java:89) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.io.fs.repcache.FSRepresentationCacheManager.openRepresentationCache(FSRepresentationCacheManager.java:59) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.io.fs.repcache.FSRepresentationCacheManagerFactory.openRepresentationCache(FSRepresentationCacheManagerFactory.java:41) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.io.fs.FSRepresentationCacheUtil.open(FSRepresentationCacheUtil.java:69) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.io.fs.FSFS.openDB(FSFS.java:360) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.io.fs.FSFS.open(FSFS.java:256) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.io.fs.FSRepository.openRepositoryRoot(FSRepository.java:830) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.io.fs.FSRepository.openRepository(FSRepository.java:797) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.io.fs.FSRepository.getLatestRevision(FSRepository.java:122) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.wc2.ng.SvnNgRepositoryAccess.getRevisionNumber(SvnNgRepositoryAccess.java:119) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.wc2.SvnRepositoryAccess.getLocations(SvnRepositoryAccess.java:180) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.wc2.ng.SvnNgRepositoryAccess.createRepositoryFor(SvnNgRepositoryAccess.java:43) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.wc2.remote.SvnRemoteGetInfo.run(SvnRemoteGetInfo.java:47) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.wc2.remote.SvnRemoteGetInfo.run(SvnRemoteGetInfo.java:31) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.internal.wc2.SvnOperationRunner.run(SvnOperationRunner.java:21) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.wc2.SvnOperationFactory.run(SvnOperationFactory.java:1239) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.wc2.SvnOperation.run(SvnOperation.java:294) [svnkit-1.10.1.jar:?]
	at org.tmatesoft.svn.core.javahl17.SVNClientImpl.info(SVNClientImpl.java:1747) [svnkit-javahl16-1.10.1.jar:?]
	at org.tmatesoft.svn.core.javahl17.SVNClientImpl.info2(SVNClientImpl.java:1725) [svnkit-javahl16-1.10.1.jar:?]
	at org.apache.subversion.javahl.SVNClient.info2(SVNClient.java:307) [svnkit-javahl16-1.10.1.jar:?]
	at com.cenqua.fisheye.svn.SvnThrottledClient$1.call(SvnThrottledClient.java:119) [fisheye.jar:?]
	at com.cenqua.fisheye.svn.SvnThrottledClient$1.call(SvnThrottledClient.java:114) [fisheye.jar:?]
	at java.util.concurrent.FutureTask.run(Unknown Source) [?:1.8.0_261]
	at com.cenqua.fisheye.svn.SvnTask.access$101(SvnTask.java:13) [fisheye.jar:?]
	at com.cenqua.fisheye.svn.SvnTask$1.run(SvnTask.java:35) [fisheye.jar:?]
	at com.cenqua.fisheye.util.NamedExecution.run(NamedExecution.java:27) [fisheye.jar:?]
	at com.cenqua.fisheye.svn.SvnTask.run(SvnTask.java:30) [fisheye.jar:?]
	... 3 more

Please, do let me know if you need any further clarifications on this matter.

Hello Felipe,
indeed we now use our issue tracker only to summarize discussions from this forum and the forum is the best place to report issues.

SVNKit doesn’t detect other processes but it uses filesysrem-based locks. As you can see from the stack trace, the error is indeed related to representations cache. So the best work-around would be to disable it:

[rep-sharing]
enable-rep-sharing = false

in REPOS/db/fsfs.conf file.

Another work-around is to set

svnkit.fsfs.repcache

JVM property to false (default: true) to disable it globally.

At the moment SVNKit doesn’t detect the situation when your repository is read-only. If representations cache is enabled, SVNKit tries to open the “rep-cache.db” database for read-write mode (it’s a cache after all). To do so it tries to obtain an exclusive (not shared) lock which is impossible to get for read-only files.

I’m not sure why we didn’t get this report [often] before as the situation seems pretty common.

If you look at FSFS.java:360, the code is pretty straightforward:

        if (myDBFormat >= MIN_REP_SHARING_FORMAT && isRepSharingAllowed) {
            myReposCacheManager = FSRepresentationCacheUtil.open(this);
        }

the isRepSharingAllowed variable reflects REPOS/db/fsfs.conf file settings but if you look inside
FSRepresentationCacheUtil#open, you’ll see that it also respects the JVM property (FSRepresentationCacheUtil#isAvailable). Finally there’re 2 rep cache manager factories (IFSRepresentationCacheManagerFactory): empty (“don’t use cache”) and “normal” (FSRepresentationCacheManagerFactory).

The “normal” factory constructs FSRepresentationCacheManager which opens the database in write mode unconditionally:

    public static IFSRepresentationCacheManager openRepresentationCache(FSFS fsfs) throws SVNException {
        final FSRepresentationCacheManager cacheObj = new FSRepresentationCacheManager();
        try {
            cacheObj.myRepCacheDB = SqlJetDb.open(fsfs.getRepositoryCacheFile(), true);
            cacheObj.myRepCacheDB.setSafetyLevel(SqlJetSafetyLevel.OFF);
            
            checkFormat(fsfs.getDBFormat(), cacheObj.myRepCacheDB);
            cacheObj.myTable = cacheObj.myRepCacheDB.getTable(REP_CACHE_TABLE);
        } catch (SqlJetException e) {
            SVNDebugLog.getDefaultLog().logError(SVNLogType.FSFS, e);
            return new FSEmptyRepresentationCacheManager();
            
        }
        return cacheObj;
    }

The “true” argument in

            cacheObj.myRepCacheDB = SqlJetDb.open(fsfs.getRepositoryCacheFile(), true);

line means “write mode”. Then locking fails.

I hope this makes the situation more clear.