@@ -64,36 +64,48 @@ export const layer = (options?: Options) =>
6464 const bus = yield * Bus . Service
6565 const fs = yield * FSUtil . Service
6666 const scope = yield * Effect . scope
67- const lock = Semaphore . makeUnsafe ( 1 )
67+ const lock = yield * Semaphore . make ( 1 )
6868 const locations = new Map < string , Map < string , Entry > > ( )
6969 const locationKey = ( location : Location . Ref ) => JSON . stringify ( [ location . directory , location . workspaceID ] )
7070
71+ const remove = ( key : string , file : string ) => {
72+ const files = locations . get ( key )
73+ const entry = files ?. get ( file )
74+ if ( ! files || ! entry ) return
75+ files . delete ( file )
76+ if ( files . size === 0 ) locations . delete ( key )
77+ return entry
78+ }
79+
7180 const stop = ( key : string , file : string ) =>
7281 Effect . suspend ( ( ) => {
73- const files = locations . get ( key )
74- const entry = files ?. get ( file )
75- if ( ! files || ! entry ) return Effect . void
76- files . delete ( file )
77- if ( files . size === 0 ) locations . delete ( key )
82+ const entry = remove ( key , file )
83+ if ( ! entry ) return Effect . void
7884 return Scope . close ( entry . scope , Exit . void )
7985 } )
8086
81- const start = Effect . fnUntraced ( function * ( input : Input ) {
87+ const start = Effect . fnUntraced ( function * ( key : string , input : Input ) {
8288 const child = yield * Scope . fork ( scope )
8389 yield * Effect . gen ( function * ( ) {
8490 const updates = yield * watcher . subscribe ( { path : input . file , type : "file" } )
8591 yield * Stream . runForEach ( updates , ( ) =>
86- fs
87- . existsSafe ( input . file )
88- . pipe (
89- Effect . flatMap ( ( exists ) =>
90- bus . publish (
91- FileSystem . Event . Changed ,
92- { file : input . file , event : exists ? "change" : "unlink" } ,
93- { location : input . location } ,
94- ) ,
95- ) ,
96- ) ,
92+ Effect . gen ( function * ( ) {
93+ const exists = yield * fs . existsSafe ( input . file )
94+ yield * bus . publish (
95+ FileSystem . Event . Changed ,
96+ { file : input . file , event : exists ? "change" : "unlink" } ,
97+ { location : input . location } ,
98+ )
99+ } ) ,
100+ )
101+ // The native watch ended (e.g. its directory vanished): forget it so the next watch starts a new one.
102+ // The scope closes from another fiber because closing it interrupts this one.
103+ yield * lock . withPermit (
104+ Effect . suspend ( ( ) => {
105+ if ( locations . get ( key ) ?. get ( input . file ) ?. scope !== child ) return Effect . void
106+ remove ( key , input . file )
107+ return Scope . close ( child , Exit . void ) . pipe ( Effect . forkIn ( scope ) , Effect . asVoid )
108+ } ) ,
97109 )
98110 } ) . pipe (
99111 Effect . catchCauseIf (
@@ -132,7 +144,7 @@ export const layer = (options?: Options) =>
132144 const soonest = [ ...full ] . reduce ( ( a , b ) => ( b [ 1 ] . expires < a [ 1 ] . expires ? b : a ) )
133145 yield * stop ( key , soonest [ 0 ] )
134146 }
135- const entry = { expires, scope : yield * start ( input ) }
147+ const entry = { expires, scope : yield * start ( key , input ) }
136148 const files = locations . get ( key ) ?? new Map < string , Entry > ( )
137149 files . set ( input . file , entry )
138150 locations . set ( key , files )
0 commit comments