resilience improvements
This commit is contained in:
+2
-2
@@ -27,7 +27,6 @@ func init() {
|
|||||||
Action: plugin.SetupCaddy,
|
Action: plugin.SetupCaddy,
|
||||||
})
|
})
|
||||||
|
|
||||||
// ... there are others. See the godoc.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CaddyHugo implements the plugin
|
// CaddyHugo implements the plugin
|
||||||
@@ -46,7 +45,8 @@ type CaddyHugo struct {
|
|||||||
|
|
||||||
authorTmpl, adminTmpl, editTmpl *template.Template
|
authorTmpl, adminTmpl, editTmpl *template.Template
|
||||||
|
|
||||||
ltime uint64
|
ltime uint64
|
||||||
|
confirmingToClient map[uint64]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build rebuilds the cached state of the site. TODO: determine if this republishes
|
// Build rebuilds the cached state of the site. TODO: determine if this republishes
|
||||||
|
|||||||
@@ -34,6 +34,67 @@ func (ch *CaddyHugo) LTime() uint64 {
|
|||||||
return ch.ltime
|
return ch.ltime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ch *CaddyHugo) ShouldApply(ltime uint64) bool {
|
||||||
|
lowest := ch.LowestPendingConfirmation()
|
||||||
|
for _, c := range ch.Confirming() {
|
||||||
|
if lowest == 0 || c < lowest {
|
||||||
|
lowest = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ltime < lowest {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ch.mtx.Lock()
|
||||||
|
defer ch.mtx.Unlock()
|
||||||
|
|
||||||
|
if _, ok := ch.confirmingToClient[ltime]; ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *CaddyHugo) ConfirmLTime(ltime uint64) {
|
||||||
|
ch.mtx.Lock()
|
||||||
|
defer ch.mtx.Unlock()
|
||||||
|
ch.confirmingToClient[ltime] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *CaddyHugo) Confirming() []uint64 {
|
||||||
|
var times []uint64
|
||||||
|
|
||||||
|
ch.mtx.Lock()
|
||||||
|
defer ch.mtx.Unlock()
|
||||||
|
|
||||||
|
for ltime := range ch.confirmingToClient {
|
||||||
|
times = append(times, ltime)
|
||||||
|
}
|
||||||
|
return times
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *CaddyHugo) LowestPendingConfirmation() uint64 {
|
||||||
|
var lowest uint64
|
||||||
|
for _, c := range ch.Confirming() {
|
||||||
|
if lowest == 0 || c < lowest {
|
||||||
|
lowest = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lowest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ch *CaddyHugo) ClearConfirmed(lowestPending uint64) {
|
||||||
|
ch.mtx.Lock()
|
||||||
|
defer ch.mtx.Unlock()
|
||||||
|
|
||||||
|
for ltime := range ch.confirmingToClient {
|
||||||
|
if ltime < lowestPending {
|
||||||
|
delete(ch.confirmingToClient, ltime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ch *CaddyHugo) DeltaWebsocket(w http.ResponseWriter, r *http.Request) (int, error) {
|
func (ch *CaddyHugo) DeltaWebsocket(w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
ReadBufferSize: 1024,
|
ReadBufferSize: 1024,
|
||||||
@@ -59,8 +120,10 @@ func (ch *CaddyHugo) DeltaWebsocket(w http.ResponseWriter, r *http.Request) (int
|
|||||||
|
|
||||||
func (ch *CaddyHugo) Message(deltas ...acedoc.Delta) Message {
|
func (ch *CaddyHugo) Message(deltas ...acedoc.Delta) Message {
|
||||||
return Message{
|
return Message{
|
||||||
Deltas: deltas,
|
Deltas: deltas,
|
||||||
LTime: ch.LTime(),
|
LTime: ch.LTime(),
|
||||||
|
Confirmed: ch.Confirming(),
|
||||||
|
LowestPending: ch.LowestPendingConfirmation(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,12 +188,19 @@ func (ch *CaddyHugo) handleDeltaConn(conn DeltaConn, doc *editSession) (int, err
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ch.ShouldApply(message.LTime) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
err = client.PushDeltas(message.Deltas...)
|
err = client.PushDeltas(message.Deltas...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errCh <- fmt.Errorf("error pushing deltas into document: %v", err)
|
errCh <- fmt.Errorf("error pushing deltas into document: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ch.ConfirmLTime(message.LTime)
|
||||||
|
ch.ClearConfirmed(message.LowestPending)
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case readMessagesCh <- message:
|
case readMessagesCh <- message:
|
||||||
case <-doneCh:
|
case <-doneCh:
|
||||||
@@ -163,6 +233,8 @@ func (ch *CaddyHugo) handleDeltaConn(conn DeltaConn, doc *editSession) (int, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Deltas []acedoc.Delta `json:"deltas"`
|
Deltas []acedoc.Delta `json:"deltas"`
|
||||||
LTime uint64 `json:"ltime"`
|
LTime uint64 `json:"ltime"`
|
||||||
|
Confirmed []uint64 `json:"confirmed"`
|
||||||
|
LowestPending uint64 `json:"lowestPending"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ import (
|
|||||||
func (ch *CaddyHugo) SetupCaddy(c *caddy.Controller) error {
|
func (ch *CaddyHugo) SetupCaddy(c *caddy.Controller) error {
|
||||||
ch.Site = httpserver.GetConfig(c)
|
ch.Site = httpserver.GetConfig(c)
|
||||||
err := ch.Setup(ch.Site.Root)
|
err := ch.Setup(ch.Site.Root)
|
||||||
|
|
||||||
|
caddy.RegisterEventHook("caddyhugo-shutdown", func(eventType caddy.EventName, eventInfo interface{}) error {
|
||||||
|
if eventType == caddy.ShutdownEvent {
|
||||||
|
return ch.persistAllEdits()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
ch.Site.AddMiddleware(ch.Middleware(c))
|
ch.Site.AddMiddleware(ch.Middleware(c))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -26,6 +34,7 @@ func (ch *CaddyHugo) Setup(dir string) error {
|
|||||||
|
|
||||||
ch.Dir = dir
|
ch.Dir = dir
|
||||||
ch.docs = make(map[string]*editSession)
|
ch.docs = make(map[string]*editSession)
|
||||||
|
ch.confirmingToClient = make(map[uint64]struct{})
|
||||||
|
|
||||||
ch.HugoCfg = &deps.DepsCfg{}
|
ch.HugoCfg = &deps.DepsCfg{}
|
||||||
|
|
||||||
|
|||||||
+30
-11
@@ -127,7 +127,7 @@ a {
|
|||||||
<div id="container" >
|
<div id="container" >
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<div id="lastSaved">
|
<div id="lastSaved">
|
||||||
<span v-if="ltime >serverLtime && (sendQueue.length > 0 || sentRecently.length > 0)">last saved ${ lastSaved.from(now) }, saving</span>
|
<span v-if="sendQueue.length > 0 || Object.keys(needConfirmation).length > 0">last saved ${ lastSaved.from(now) }, saving</span>
|
||||||
<span v-else>saved</span>
|
<span v-else>saved</span>
|
||||||
<span v-if="connectionError">, ${connectionError}</span>
|
<span v-if="connectionError">, ${connectionError}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -158,13 +158,14 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var uiBindings = {
|
var uiBindings = {
|
||||||
ltime: 0,
|
ltime: {{ .LTime }},
|
||||||
serverLtime: 0,
|
serverLtime: 0,
|
||||||
lastSaved: moment(),
|
lastSaved: moment(),
|
||||||
now: moment(),
|
now: moment(),
|
||||||
connectionError: null,
|
connectionError: null,
|
||||||
sendQueue: [],
|
sendQueue: [],
|
||||||
sentRecently: [],
|
sentRecently: [],
|
||||||
|
needConfirmation: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
var app = new Vue({
|
var app = new Vue({
|
||||||
@@ -178,10 +179,12 @@ a {
|
|||||||
return uiBindings.ltime
|
return uiBindings.ltime
|
||||||
}
|
}
|
||||||
|
|
||||||
function observeServer(l) {
|
function observeServer(l, confirmed) {
|
||||||
uiBindings.serverLtime = l;
|
uiBindings.serverLtime = l;
|
||||||
while (uiBindings.sentRecently.length > 0 && uiBindings.sentRecently[0].ltime < l) {
|
if (confirmed && confirmed.length > 0) {
|
||||||
uiBindings.sentRecently.pop();
|
confirmed.forEach(function (e) {
|
||||||
|
delete uiBindings.needConfirmation[e];
|
||||||
|
})
|
||||||
}
|
}
|
||||||
observe(l);
|
observe(l);
|
||||||
}
|
}
|
||||||
@@ -231,20 +234,36 @@ a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uiBindings.now = moment();
|
uiBindings.now = moment();
|
||||||
if (uiBindings.connectionError) {
|
if (uiBindings.connectionError) {
|
||||||
socket = connect();
|
socket = connect();
|
||||||
} else if (uiBindings.sendQueue.length > 0) {
|
} else if (uiBindings.sendQueue.length > 0) {
|
||||||
var ltime = getLtime();
|
var ltime = getLtime();
|
||||||
socket.send(JSON.stringify({
|
|
||||||
|
// record lowest pending
|
||||||
|
// ltime at the time this message
|
||||||
|
// was serialized
|
||||||
|
var lowestPending = ltime;
|
||||||
|
for (c in uiBindings.needConfirmation) {
|
||||||
|
c = parseInt(c, 10);
|
||||||
|
if (lowestPending === 0 || c < lowestPending) {
|
||||||
|
lowestPending = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = JSON.stringify({
|
||||||
"deltas": uiBindings.sendQueue,
|
"deltas": uiBindings.sendQueue,
|
||||||
"ltime": ltime,
|
"ltime": ltime,
|
||||||
}));
|
"lowestPending": lowestPending,
|
||||||
uiBindings.sentRecently.push({
|
|
||||||
"ltime": ltime,
|
|
||||||
"sent": uiBindings.sendQueue,
|
|
||||||
});
|
});
|
||||||
uiBindings.sendQueue = [];
|
uiBindings.sendQueue = [];
|
||||||
|
uiBindings.needConfirmation[ltime] = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ltime in uiBindings.needConfirmation) {
|
||||||
|
var msg = uiBindings.needConfirmation[ltime];
|
||||||
|
socket.send(msg);
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
@@ -254,7 +273,7 @@ a {
|
|||||||
// Listen for messages
|
// Listen for messages
|
||||||
socket.addEventListener('message', function (event) {
|
socket.addEventListener('message', function (event) {
|
||||||
var message = JSON.parse(event.data);
|
var message = JSON.parse(event.data);
|
||||||
observeServer(message.ltime);
|
observeServer(message.ltime, message.confirmed);
|
||||||
|
|
||||||
var deltas = [];
|
var deltas = [];
|
||||||
deltas.push.apply(deltas, message.deltas);
|
deltas.push.apply(deltas, message.deltas);
|
||||||
|
|||||||
Reference in New Issue
Block a user