private string InsertEntry(string before, string after, Clock time = null) { if (time is null) { time = this.curTime; } string opid = time.ToString(); StateHisotryEntry newEntry = new StateHisotryEntry(this.uid, opid, before, after, time.ToString()); this.log.Add(opid, newEntry); foreach (var i in this.heads) { // link the new one and old ones, may converging the states newEntry.prev.Add(i); this.log[i].aft.Add(opid); } // reset head this.heads.Clear(); this.heads.Add(opid); Sync(newEntry); time.Increment(); this.curTime = time; return(opid); }
/// <summary> /// Search through ops happens between startop and endop time // can be used by CRDT OPs /// </summary> /// <param name="starttime"></param> /// <param name="endtime"></param> /// <returns>opids of found ops</returns> public List <string> CasualSearch(string starttime, string endtime) { List <string> res = new List <string>(); Clock st = Clock.FromString(starttime); Clock et = Clock.FromString(endtime); // BFS from start Queue <string> Q = new Queue <string>(); Q.Enqueue(starttime); List <string> concurrents = new List <string>(); while (Q.Count > 0) { string opid = Q.Dequeue(); StateHisotryEntry op = this.log[opid]; Clock optime = Clock.FromString(op.time); if ((optime.CompareVectorClock(st) == 1 && optime.CompareVectorClock(et) < 1) || (optime.ToString().Equals(starttime))) { // a new level (BFS) if (!op.revflag) { if (concurrents.Count == 0) { concurrents.Add(opid); } else { // if concurrent with other op in concurrents if (optime.CompareVectorClock(Clock.FromString(this.log[concurrents[0]].time)) == 0) { concurrents.Add(opid); } else // next level { res.AddRange(ResolveConcurrent(concurrents)); concurrents.Clear(); concurrents.Add(opid); } } } foreach (var i in op.aft) { if (!Q.Contains(i)) { Q.Enqueue(i); } } } } res.AddRange(ResolveConcurrent(concurrents)); return(res); }
/// <summary> /// Get an entry in string form. /// </summary> /// <param name="opid"></param> /// <param name="before"></param> /// <param name="after"></param> /// <param name="time"></param> public void GetEntry(string opid, out string before, out string after, out Clock time) { StateHisotryEntry item = this.log[opid]; before = item.before; after = item.after; time = Clock.FromString(item.time); }
/// <summary> /// Get an entry and cast to a Payload object. /// </summary> /// <param name="opid"></param> /// <param name="stringToPayload"></param> /// <param name="before"></param> /// <param name="after"></param> /// <param name="time"></param> /// <typeparam name="T"></typeparam> public void GetEntry <T>(string opid, StringToPayloadDelegate <T> stringToPayload, out T before, out T after, out Clock time) { StateHisotryEntry item = this.log[opid]; before = stringToPayload(item.before); after = stringToPayload(item.after); time = Clock.FromString(item.time); }
/// <summary> /// Handles received op /// </summary> /// <param name="otherop"></param> /// <param name="status"></param> public void Merge(string otherop, int status) { if (status == 0) { StateHisotryEntry op = JsonConvert.DeserializeObject <StateHisotryEntry>(otherop); DEBUG("Merging new op " + otherop); Clock newtime = Clock.FromString(op.time); curTime.Merge(newtime); // if an empty place holder already created to hold // the pointers, update that string opid = op.opid; if (this.log.ContainsKey(opid)) { op.prev.AddRange(this.log[opid].prev); } this.log[op.opid] = op; // update the pointers foreach (var i in op.prev) { // in case prev op is not sync'd yet, create a placeholder, see above also if (!this.log.ContainsKey(i)) { StateHisotryEntry newEntry = new StateHisotryEntry(this.uid, i, "", "", ""); this.log[i] = newEntry; } this.log[i].aft.Add(opid); } } else if (status == 1) { DEBUG("Merging tombstone op " + otherop); this.tombstone.Add(otherop); } else if (status == 2) { StateHisotryEntry op = JsonConvert.DeserializeObject <StateHisotryEntry>(otherop); DEBUG("Merging new related op " + otherop); foreach (var r in op.related) { // hashset automatically remove duplicate this.log[op.opid].related.Add(r); } } }
/// <summary> /// Called on every update of CRDT OP to /// synchronize the history /// </summary> /// <param name="newop"></param> /// <param name="status">0 = op, 1 = tombstone, 2 = related</param> public void Sync(StateHisotryEntry newop, int status = 0) { DEBUG("Syncing new op " + newop.opid); string json = JsonConvert.SerializeObject(newop, Formatting.Indented); Responses res = new Responses(Status.success); Parameters syncPm = new Parameters(2); syncPm.AddParam(0, json); syncPm.AddParam(1, status); string broadcast = Parser.BuildCommand("h", "y", this.uid, syncPm); res.AddResponse(Dest.broadcast, broadcast, false); Global.server.StageResponse(res); }
/// <summary> /// Search through ops happens between startop and endop time // can be used by CRDT OPs /// </summary> /// <param name="startime"></param> /// <param name="endtime"></param> /// <returns>opids of found ops</returns> public List <string> CasualSearch(Clock starttime, Clock endtime) { List <string> res = new List <string>(); //linear search foreach (var item in this.log) { StateHisotryEntry op = item.Value; Clock optime = Clock.FromString(op.time); // op exactly the same as start date, // after start time, // and before/concurrent of endtime if ((optime.CompareVectorClock(starttime) == 1 && optime.CompareVectorClock(endtime) < 1) || (optime.ToString().Equals(starttime.ToString()))) { res.Add(op.opid); } } return(res); }