@@ -24,37 +24,45 @@ class SingleInstance(
2424 //
2525 // This is intentionally unhandled because this situation could happen even if dial succeeds -- the dial
2626 // could succeed just *before* the other instance terminates.
27- tryListen()?.let { listener ->
27+ try {
28+ // Throws AlreadyLockedException if lock can't be acquired
29+ val listener = tryListen()
30+
2831 // Listening, accept incoming dials indefinitely
2932 CoroutineScope (Dispatchers .IO ).launch {
3033 accept(listener, onArgsReceived)
3134 }
32- } ? : run {
35+
36+ } catch (e: AlreadyLockedException ) {
3337 // Another instance is listening, dial and send args
3438 dial(args)
3539 onExit()
3640 }
3741 }
3842
39- private fun tryListen (): ServerSocketChannel ? {
43+ private fun tryListen (): ServerSocketChannel {
4044 // Acquire an exclusive lock, which will be auto-released by the OS on process death.
4145 // Lock protects the domain socket
4246 val lockPath = " $socketPath .lock"
43- lock = FileChannel .open(Path (lockPath), StandardOpenOption .WRITE , StandardOpenOption .CREATE )
44- .tryLock() ? : return null
47+ val fileChannel = FileChannel .open(Path (lockPath), StandardOpenOption .WRITE , StandardOpenOption .CREATE )
48+ lock = fileChannel .tryLock() ? : throw AlreadyLockedException (lockPath)
4549
4650 // Lock acquired, no other instances are running. Safe to clean up existing socket to allow for bind.
4751 File (socketPath).delete()
4852
49- return ServerSocketChannel .open(StandardProtocolFamily .UNIX ).apply {
50- bind(UnixDomainSocketAddress .of(socketPath))
51- }
53+ val channel = ServerSocketChannel .open(StandardProtocolFamily .UNIX )
54+ val address = UnixDomainSocketAddress .of(socketPath)
55+ channel.bind(address)
56+
57+ return channel
5258 }
5359 // Keep a reference to the lock, which prevents the channel from being auto-closed (which would release the lock)
5460 private var lock: FileLock ? = null
5561
5662 private fun dial (args : Array <String >) {
57- writeArgs(SocketChannel .open(UnixDomainSocketAddress .of(socketPath)), args)
63+ val address = UnixDomainSocketAddress .of(socketPath)
64+ val channel = SocketChannel .open(address)
65+ writeArgs(channel, args)
5866 }
5967
6068 private fun accept (listener : ServerSocketChannel , onArgsReceived : suspend (Array <String >) -> Unit ) {
@@ -108,6 +116,8 @@ class SingleInstance(
108116 }
109117}
110118
119+ internal class AlreadyLockedException (path : String ) : Exception(" $path already locked" )
120+
111121/* * Provides a socket path in the temp dir for the provided identifier.
112122 * Identifier must not contain characters which are unsuitable for a file name.
113123 *
0 commit comments