Browse Source

resilience improvements

Stephen Searles 1 year ago
parent
commit
4b76df1811
4 changed files with 117 additions and 17 deletions
  1. 2
    2
      caddyhugo.go
  2. 76
    4
      deltas.go
  3. 9
    0
      setup.go
  4. 30
    11
      templates.go

+ 2
- 2
caddyhugo.go View File

@@ -27,7 +27,6 @@ func init() {
27 27
 		Action:     plugin.SetupCaddy,
28 28
 	})
29 29
 
30
-	// ... there are others. See the godoc.
31 30
 }
32 31
 
33 32
 // CaddyHugo implements the plugin
@@ -46,7 +45,8 @@ type CaddyHugo struct {
46 45
 
47 46
 	authorTmpl, adminTmpl, editTmpl *template.Template
48 47
 
49
-	ltime uint64
48
+	ltime              uint64
49
+	confirmingToClient map[uint64]struct{}
50 50
 }
51 51
 
52 52
 // Build rebuilds the cached state of the site. TODO: determine if this republishes

+ 76
- 4
deltas.go View File

@@ -34,6 +34,67 @@ func (ch *CaddyHugo) LTime() uint64 {
34 34
 	return ch.ltime
35 35
 }
36 36
 
37
+func (ch *CaddyHugo) ShouldApply(ltime uint64) bool {
38
+	lowest := ch.LowestPendingConfirmation()
39
+	for _, c := range ch.Confirming() {
40
+		if lowest == 0 || c < lowest {
41
+			lowest = c
42
+		}
43
+	}
44
+
45
+	if ltime < lowest {
46
+		return false
47
+	}
48
+
49
+	ch.mtx.Lock()
50
+	defer ch.mtx.Unlock()
51
+
52
+	if _, ok := ch.confirmingToClient[ltime]; ok {
53
+		return false
54
+	}
55
+
56
+	return true
57
+}
58
+
59
+func (ch *CaddyHugo) ConfirmLTime(ltime uint64) {
60
+	ch.mtx.Lock()
61
+	defer ch.mtx.Unlock()
62
+	ch.confirmingToClient[ltime] = struct{}{}
63
+}
64
+
65
+func (ch *CaddyHugo) Confirming() []uint64 {
66
+	var times []uint64
67
+
68
+	ch.mtx.Lock()
69
+	defer ch.mtx.Unlock()
70
+
71
+	for ltime := range ch.confirmingToClient {
72
+		times = append(times, ltime)
73
+	}
74
+	return times
75
+}
76
+
77
+func (ch *CaddyHugo) LowestPendingConfirmation() uint64 {
78
+	var lowest uint64
79
+	for _, c := range ch.Confirming() {
80
+		if lowest == 0 || c < lowest {
81
+			lowest = c
82
+		}
83
+	}
84
+	return lowest
85
+}
86
+
87
+func (ch *CaddyHugo) ClearConfirmed(lowestPending uint64) {
88
+	ch.mtx.Lock()
89
+	defer ch.mtx.Unlock()
90
+
91
+	for ltime := range ch.confirmingToClient {
92
+		if ltime < lowestPending {
93
+			delete(ch.confirmingToClient, ltime)
94
+		}
95
+	}
96
+}
97
+
37 98
 func (ch *CaddyHugo) DeltaWebsocket(w http.ResponseWriter, r *http.Request) (int, error) {
38 99
 	var upgrader = websocket.Upgrader{
39 100
 		ReadBufferSize:  1024,
@@ -59,8 +120,10 @@ func (ch *CaddyHugo) DeltaWebsocket(w http.ResponseWriter, r *http.Request) (int
59 120
 
60 121
 func (ch *CaddyHugo) Message(deltas ...acedoc.Delta) Message {
61 122
 	return Message{
62
-		Deltas: deltas,
63
-		LTime:  ch.LTime(),
123
+		Deltas:        deltas,
124
+		LTime:         ch.LTime(),
125
+		Confirmed:     ch.Confirming(),
126
+		LowestPending: ch.LowestPendingConfirmation(),
64 127
 	}
65 128
 }
66 129
 
@@ -125,12 +188,19 @@ func (ch *CaddyHugo) handleDeltaConn(conn DeltaConn, doc *editSession) (int, err
125 188
 				continue
126 189
 			}
127 190
 
191
+			if !ch.ShouldApply(message.LTime) {
192
+				continue
193
+			}
194
+
128 195
 			err = client.PushDeltas(message.Deltas...)
129 196
 			if err != nil {
130 197
 				errCh <- fmt.Errorf("error pushing deltas into document: %v", err)
131 198
 				return
132 199
 			}
133 200
 
201
+			ch.ConfirmLTime(message.LTime)
202
+			ch.ClearConfirmed(message.LowestPending)
203
+
134 204
 			select {
135 205
 			case readMessagesCh <- message:
136 206
 			case <-doneCh:
@@ -163,6 +233,8 @@ func (ch *CaddyHugo) handleDeltaConn(conn DeltaConn, doc *editSession) (int, err
163 233
 }
164 234
 
165 235
 type Message struct {
166
-	Deltas []acedoc.Delta `json:"deltas"`
167
-	LTime  uint64         `json:"ltime"`
236
+	Deltas        []acedoc.Delta `json:"deltas"`
237
+	LTime         uint64         `json:"ltime"`
238
+	Confirmed     []uint64       `json:"confirmed"`
239
+	LowestPending uint64         `json:"lowestPending"`
168 240
 }

+ 9
- 0
setup.go View File

@@ -17,6 +17,14 @@ import (
17 17
 func (ch *CaddyHugo) SetupCaddy(c *caddy.Controller) error {
18 18
 	ch.Site = httpserver.GetConfig(c)
19 19
 	err := ch.Setup(ch.Site.Root)
20
+
21
+	caddy.RegisterEventHook("caddyhugo-shutdown", func(eventType caddy.EventName, eventInfo interface{}) error {
22
+		if eventType == caddy.ShutdownEvent {
23
+			return ch.persistAllEdits()
24
+		}
25
+		return nil
26
+	})
27
+
20 28
 	ch.Site.AddMiddleware(ch.Middleware(c))
21 29
 	return err
22 30
 }
@@ -26,6 +34,7 @@ func (ch *CaddyHugo) Setup(dir string) error {
26 34
 
27 35
 	ch.Dir = dir
28 36
 	ch.docs = make(map[string]*editSession)
37
+	ch.confirmingToClient = make(map[uint64]struct{})
29 38
 
30 39
 	ch.HugoCfg = &deps.DepsCfg{}
31 40
 

+ 30
- 11
templates.go View File

@@ -127,7 +127,7 @@ a {
127 127
 <div id="container" >
128 128
 	<div id="header">
129 129
 		<div id="lastSaved">
130
-			<span v-if="ltime >serverLtime && (sendQueue.length > 0 || sentRecently.length > 0)">last saved ${ lastSaved.from(now) }, saving</span>
130
+			<span v-if="sendQueue.length > 0 || Object.keys(needConfirmation).length > 0">last saved ${ lastSaved.from(now) }, saving</span>
131 131
 			<span v-else>saved</span>
132 132
 			<span v-if="connectionError">, ${connectionError}</span>
133 133
 		</div>
@@ -158,13 +158,14 @@ a {
158 158
     }
159 159
 
160 160
     var uiBindings = {
161
-	    ltime: 0,
161
+	    ltime: {{ .LTime }},
162 162
 	    serverLtime: 0,
163 163
 	    lastSaved: moment(),
164 164
 	    now: moment(),
165 165
 	    connectionError: null,
166 166
 	    sendQueue: [],
167 167
 	    sentRecently: [],
168
+	    needConfirmation: {},
168 169
     };
169 170
 
170 171
     var app = new Vue({
@@ -178,10 +179,12 @@ a {
178 179
 	    return uiBindings.ltime
179 180
     }
180 181
 
181
-    function observeServer(l) {
182
+    function observeServer(l, confirmed) {
182 183
 	    uiBindings.serverLtime = l;
183
-	    while (uiBindings.sentRecently.length > 0 && uiBindings.sentRecently[0].ltime < l) {
184
-		    uiBindings.sentRecently.pop();
184
+	    if (confirmed && confirmed.length > 0) {
185
+		    confirmed.forEach(function (e) {
186
+			    delete uiBindings.needConfirmation[e];
187
+		    })
185 188
 	    }
186 189
 	    observe(l);
187 190
     }
@@ -231,20 +234,36 @@ a {
231 234
 			    }
232 235
 		    }
233 236
 	    }
237
+
234 238
 	    uiBindings.now = moment();
235 239
 	    if (uiBindings.connectionError) {
236 240
 		    socket = connect();
237 241
 	    } else if (uiBindings.sendQueue.length > 0) {
238 242
 		    var ltime = getLtime();
239
-		    socket.send(JSON.stringify({
243
+
244
+		    // record lowest pending
245
+		    // ltime at the time this message
246
+		    // was serialized
247
+		    var lowestPending = ltime;
248
+		    for (c in uiBindings.needConfirmation) {
249
+			    c = parseInt(c, 10);
250
+			    if (lowestPending === 0 || c < lowestPending) {
251
+				    lowestPending = c;
252
+			    }
253
+		    }
254
+
255
+		    var msg = JSON.stringify({
240 256
 			    "deltas": uiBindings.sendQueue,
241 257
 			    "ltime": ltime,
242
-		    }));
243
-		    uiBindings.sentRecently.push({
244
-			    "ltime": ltime,
245
-			    "sent": uiBindings.sendQueue,
258
+			    "lowestPending": lowestPending,
246 259
 		    });
247 260
 		    uiBindings.sendQueue = [];
261
+		    uiBindings.needConfirmation[ltime] = msg;
262
+	    }
263
+
264
+	    for (ltime in uiBindings.needConfirmation) {
265
+		    var msg = uiBindings.needConfirmation[ltime];
266
+		    socket.send(msg);
248 267
 	    }
249 268
     }, 500);
250 269
 
@@ -254,7 +273,7 @@ a {
254 273
 	    // Listen for messages
255 274
 	    socket.addEventListener('message', function (event) {
256 275
 		 var message = JSON.parse(event.data);
257
-		 observeServer(message.ltime);
276
+		 observeServer(message.ltime, message.confirmed);
258 277
 
259 278
 		 var deltas = [];
260 279
 		 deltas.push.apply(deltas, message.deltas);

Loading…
Cancel
Save