fixed doSync bug, fixed connection problems, improved p2p sync method - still

there are some cases that may lead to inconsistencies. Currently, only the master-slave method is a reliable sync method
This commit is contained in:
DadaMonad
2015-02-05 10:46:40 +00:00
parent 58a479be9b
commit 3eb933400a
21 changed files with 631 additions and 330 deletions

View File

@@ -10,8 +10,13 @@ adaptConnector = (connector, engine, HB, execution_listener)->
for name, f of ConnectorClass
connector[name] = f
connector.setIsBoundToY()
send_ = (o)->
if o.uid.creator is HB.getUserId() and (typeof o.uid.op_number isnt "string")
if (o.uid.creator is HB.getUserId()) and
(typeof o.uid.op_number isnt "string") and # TODO: i don't think that we need this anymore..
(o.uid.doSync is "true" or o.uid.doSync is true) and # TODO: ensure, that only true is valid
(HB.getUserId() isnt "_temp")
connector.broadcast o
if connector.invokeSync?
@@ -36,15 +41,13 @@ adaptConnector = (connector, engine, HB, execution_listener)->
getHB = (v)->
state_vector = parse_state_vector v
hb = HB._encode state_vector
for o in hb
o.fromHB = "true" # execute immediately
json =
hb: hb
state_vector: encode_state_vector HB.getOperationCounter()
json
applyHB = (hb)->
engine.applyOp hb
applyHB = (hb, fromHB)->
engine.applyOp hb, fromHB
connector.getStateVector = getStateVector
connector.getHB = getHB
@@ -55,6 +58,5 @@ adaptConnector = (connector, engine, HB, execution_listener)->
if op.uid.creator isnt HB.getUserId()
engine.applyOp op
connector.setIsBoundToY()
module.exports = adaptConnector

View File

@@ -27,8 +27,6 @@ module.exports =
# is set to true when this is synced with all other connections
@is_synced = false
# true, iff the client is currently syncing
@is_syncing = false
# Peerjs Connections: key: conn-id, value: object
@connections = {}
# List of functions that shall process incoming data
@@ -38,6 +36,7 @@ module.exports =
@is_bound_to_y = false
@connections = {}
@current_sync_target = null
@sent_hb_to_all_users = false
isRoleMaster: ->
@role is "master"
@@ -52,6 +51,8 @@ module.exports =
if not c.is_synced
@performSync user
break
if not @current_sync_target?
@setStateSynced()
null
userLeft: (user)->
@@ -60,7 +61,7 @@ module.exports =
userJoined: (user, role)->
if not role?
throw new Error "Internal: You must specify the role of the joined user!"
throw new Error "Internal: You must specify the role of the joined user! E.g. userJoined('uid:3939','slave')"
# a user joined the room
@connections[user] =
is_synced : false
@@ -115,32 +116,49 @@ module.exports =
@current_sync_target = user
@send user,
sync_step: "getHB"
data: @getStateVector()
send_again: "true"
data: [] # @getStateVector()
if not @sent_hb_to_all_users
@sent_hb_to_all_users = true
hb = @getHB([]).hb
_hb = []
for o in hb
_hb.push o
if _hb.length > 30
@broadcast
sync_step: "applyHB_"
data: _hb
_hb = []
@broadcast
sync_step: "applyHB"
data: _hb
#
# When a master node joined the room, perform this sync with him. It will ask the master for the HB,
# and will broadcast his own HB
#
performSyncWithMaster: (user)->
if not @is_syncing
@current_sync_target = user
@is_syncing = true
@send user,
sync_step: "getHB"
send_again: "true"
data: []
hb = @getHB([]).hb
_hb = []
for o in hb
_hb.push o
if _hb.length > 30
@broadcast
sync_step: "applyHB_"
data: _hb
_hb = []
@broadcast
sync_step: "applyHB"
data: _hb
@current_sync_target = user
@send user,
sync_step: "getHB"
send_again: "true"
data: []
hb = @getHB([]).hb
_hb = []
for o in hb
_hb.push o
if _hb.length > 30
@broadcast
sync_step: "applyHB_"
data: _hb
_hb = []
@broadcast
sync_step: "applyHB"
data: _hb
#
# You are sure that all clients are synced, call this function.
#
@@ -160,25 +178,36 @@ module.exports =
for f in @receive_handlers
f sender, res
else
if sender is @user_id
return
if res.sync_step is "getHB"
data = @getHB(res.data)
hb = data.hb
_hb = []
# always broadcast, when not synced.
# This reduces errors, when the clients goes offline prematurely.
# When this client only syncs to one other clients, but looses connectors,
# before syncing to the other clients, the online clients have different states.
# Since we do not want to perform regular syncs, this is a good alternative
if @is_synced
sendApplyHB = ()->
for o in hb
sendApplyHB = (m)=>
@send sender, m
else
sendApplyHB = (m)=>
@broadcast m
for o in hb
_hb.push o
if _hb.length > 30
@send sender,
sendApplyHB
sync_step: "applyHB_"
data: _hb
_hb = []
if @is_synced
@send sender,
sync_s tep: "applyHB"
data: _hb
sendApplyHB
sync_step : "applyHB"
data: _hb
if res.send_again?
send_again = do (sv = data.state_vector)=>
()=>
@@ -189,15 +218,14 @@ module.exports =
sent_again: "true"
setTimeout send_again, 3000
else if res.sync_step is "applyHB"
@applyHB(res.data)
@applyHB(res.data, sender is @current_sync_target)
if (@syncMode is "syncAll" or res.sent_again?) and not @is_synced
@setStateSynced()
if (@syncMode is "syncAll" or res.sent_again?) and (not @is_synced) and (@current_sync_target is sender)
@connections[sender].is_synced = true
@findNewSyncTarget()
else if res.sync_step is "applyHB_"
@applyHB(res.data)
@applyHB(res.data, sender is @current_sync_target)
# Currently, the HB encodes operations as JSON. For the moment I want to keep it

View File

@@ -61,10 +61,12 @@ class Engine
# TODO: make this more efficient!!
# - operations may only executed in order by creator, order them in object of arrays (key by creator)
# - you can probably make something like dependencies (creator1 waits for creator2)
applyOp: (op_json_array)->
applyOp: (op_json_array, fromHB = false)->
if op_json_array.constructor isnt Array
op_json_array = [op_json_array]
for op_json in op_json_array
if fromHB
op_json.fromHB = "true" # execute immediately, if
# $parse_and_execute will return false if $o_json was parsed and executed, otherwise the parsed operadion
o = @parseOperation op_json
if op_json.fromHB?

View File

@@ -26,7 +26,10 @@ class HistoryBuffer
own = @buffer[@user_id]
if own?
for o_name,o of own
o.uid.creator = id
if o.uid.creator?
o.uid.creator = id
if o.uid.alt?
o.uid.alt.creator = id
if @buffer[id]?
throw new Error "You are re-assigning an old user id - this is not (yet) possible!"
@buffer[id] = own
@@ -114,7 +117,7 @@ class HistoryBuffer
for u_name,user of @buffer
# TODO next, if @state_vector[user] <= state_vector[user]
for o_number,o of user
if o.uid.doSync and unknown(u_name, o_number)
if (not o.uid.noOperation?) and o.uid.doSync and unknown(u_name, o_number)
# its necessary to send it, and not known in state_vector
o_json = o._encode()
if o.next_cl? # applies for all ops but the most right delimiter!

View File

@@ -111,7 +111,13 @@ module.exports = (HB)->
if not @uid.noOperation?
@uid
else
@uid.alt # could be (safely) undefined
if @uid.alt? # could be (safely) undefined
map_uid = @uid.alt.cloneUid()
map_uid.sub = @uid.sub
map_uid.doSync = false
map_uid
else
undefined
cloneUid: ()->
uid = {}

View File

@@ -56,11 +56,10 @@ module.exports = (HB)->
event_properties =
name: property_name
event_this = @
map_uid = @cloneUid()
map_uid.sub = property_name
rm_uid =
noOperation: true
alt: map_uid
sub: property_name
alt: @
rm = new types.ReplaceManager event_properties, event_this, rm_uid # this operation shall not be saved in the HB
@map[property_name] = rm
rm.setParent @, property_name