@@ -9,12 +9,12 @@ import (
9
9
"os/exec"
10
10
"path"
11
11
"path/filepath"
12
+ "sync"
12
13
"time"
13
14
14
15
"github.com/gorilla/websocket"
15
16
"github.com/rjeczalik/notify"
16
17
"go.coder.com/flog"
17
- "golang.org/x/crypto/ssh/terminal"
18
18
"golang.org/x/xerrors"
19
19
20
20
"cdr.dev/coder-cli/internal/entclient"
@@ -167,51 +167,51 @@ func (s Sync) work(ev timedEvent) {
167
167
}
168
168
}
169
169
170
- func setConsoleTitle (title string ) {
171
- if ! terminal .IsTerminal (int (os .Stdout .Fd ())) {
172
- return
173
- }
174
- fmt .Printf ("\033 ]0;%s\007 " , title )
175
- }
176
-
177
170
178
171
var ErrRestartSync = errors .New ("the sync exited because it was overloaded, restart it" )
179
172
180
173
// workEventGroup converges a group of events to prevent duplicate work.
181
174
func (s Sync ) workEventGroup (evs []timedEvent ) {
182
- cache := map [ string ] timedEvent {}
175
+ cache := make ( eventCache )
183
176
for _ , ev := range evs {
184
- log := flog .New ()
185
- log .Prefix = ev .Path () + ": "
186
- lastEvent , ok := cache [ev .Path ()]
187
- if ok {
188
- switch {
189
- // If the file was quickly created and then destroyed, pretend nothing ever happened.
190
- case lastEvent .Event () == notify .Create && ev .Event () == notify .Remove :
191
- delete (cache , ev .Path ())
192
- log .Info ("ignored Create then Remove" )
193
- continue
194
- }
195
- }
196
- if ok {
197
- log .Info ("ignored duplicate event (%s replaced by %s)" , lastEvent .Event (), ev .Event ())
198
- }
199
- // Only let the latest event for a path have action.
200
- cache [ev .Path ()] = ev
177
+ cache .Add (ev )
201
178
}
202
- for _ , ev := range cache {
203
- setConsoleTitle ("🚀 updating " + filepath .Base (ev .Path ()))
179
+
180
+ // We want to process events concurrently but safely for speed.
181
+ // Because the event cache prevents duplicate events for the same file, race conditions of that type
182
+ // are impossible.
183
+ // What is possible is a dependency on a previous Rename or Create. For example, if a directory is renamed
184
+ // and then a file is moved to it. AFAIK this dependecy only exists with Directories.
185
+ // So, we sequentially process the list of directory Renames and Creates, and then concurrently
186
+ // perform all Writes.
187
+ for _ , ev := range cache .DirectoryEvents () {
204
188
s .work (ev )
205
189
}
206
- }
207
190
191
+ var wg sync.WaitGroup
192
+ for _ , ev := range cache .FileEvents () {
193
+ setConsoleTitle (fmtUpdateTitle (ev .Path ()))
194
+
195
+ wg .Add (1 )
196
+ ev := ev
197
+ go func () {
198
+ defer wg .Done ()
199
+ s .work (ev )
200
+ }()
201
+ }
202
+
203
+ wg .Wait ()
204
+ }
208
205
209
206
const (
210
207
// maxinflightInotify sets the maximum number of inotifies before the sync just restarts.
211
208
// Syncing a large amount of small files (e.g .git or node_modules) is impossible to do performantly
212
209
// with individual rsyncs.
213
210
maxInflightInotify = 8
214
- maxEventDelay = time .Second * 7
211
+ maxEventDelay = time .Second * 7
212
+ // maxAcceptableDispatch is the maximum amount of time before an event should begin its journey to the server.
213
+ // This sets a lower bound for perceivable latency, but the higher it is, the better the optimization.
214
+ maxAcceptableDispatch = time .Millisecond * 50
215
215
)
216
216
217
217
func (s Sync ) Run () error {
@@ -250,7 +250,7 @@ func (s Sync) Run() error {
250
250
251
251
var (
252
252
eventGroup []timedEvent
253
- dispatchEventGroup = time .NewTicker (time . Millisecond * 10 )
253
+ dispatchEventGroup = time .NewTicker (maxAcceptableDispatch )
254
254
)
255
255
defer dispatchEventGroup .Stop ()
256
256
for {
0 commit comments