github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/controllers/core/filewatch/fsevent/coalesce.go (about) 1 package fsevent 2 3 import ( 4 "time" 5 6 "github.com/tilt-dev/tilt/internal/watch" 7 ) 8 9 // BufferMinRestDuration bundles many file changes together by waiting a short amount of time after a file 10 // change before emitting an event to the channel. 11 // 12 // 200ms is not the result of any kind of research or experimentation 13 // it might end up being a significant part of deployment delay, if we get the total latency <2s 14 // it might also be long enough that it misses some changes if the user has some operation involving a large file 15 // 16 // (e.g., a binary dependency in git), but that's hopefully less of a problem since we'd get it in the next build 17 const BufferMinRestDuration = 200 * time.Millisecond 18 19 // BufferMaxDuration prevents excessive delays when bundling together file changes by emitting an event to the 20 // channel if the threshold is reached even if new file changes are still coming in. 21 const BufferMaxDuration = 10 * time.Second 22 23 // Coalesce makes an attempt to read some events from `eventChan` so that multiple file changes 24 // that happen at the same time from the user's perspective are grouped together. 25 func Coalesce(timerMaker TimerMaker, eventChan <-chan watch.FileEvent) <-chan []watch.FileEvent { 26 ret := make(chan []watch.FileEvent) 27 go func() { 28 defer close(ret) 29 30 for { 31 event, ok := <-eventChan 32 if !ok { 33 return 34 } 35 events := []watch.FileEvent{event} 36 37 // keep grabbing changes until we've gone `BufferMinRestDuration` without seeing a change 38 minRestTimer := timerMaker(BufferMinRestDuration) 39 40 // but if we go too long before seeing a break (e.g., a process is constantly writing logs to that dir) 41 // then just send what we've got 42 timeout := timerMaker(BufferMaxDuration) 43 44 done := false 45 channelClosed := false 46 for !done && !channelClosed { 47 select { 48 case event, ok := <-eventChan: 49 if !ok { 50 channelClosed = true 51 } else { 52 minRestTimer = timerMaker(BufferMinRestDuration) 53 events = append(events, event) 54 } 55 case <-minRestTimer: 56 done = true 57 case <-timeout: 58 done = true 59 } 60 } 61 if len(events) > 0 { 62 ret <- events 63 } 64 65 if channelClosed { 66 return 67 } 68 } 69 70 }() 71 return ret 72 }