Skip to content

Instantly share code, notes, and snippets.

@karlwestin
Created June 30, 2015 16:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karlwestin/9e6bf87b897b18cda700 to your computer and use it in GitHub Desktop.
Save karlwestin/9e6bf87b897b18cda700 to your computer and use it in GitHub Desktop.
Porting the erlang checkpoint comparison to JS

Porting CouchDB checkPoints from erlang to javascript

This is an attempt to implement CouchDB checkpoint comparison for PouchDB, to be able to pick up interrupted replications even though checkpoint writing is in an unknown state. Mismatching checkpoints causes replication to start over from change 0

This gist is a request for comments from people familiar with both JS and Erlang, to see if they are functionally identical

var LOWEST_SEQ = 0;
function compareReplicationLogs(srcDoc, tgtDoc) {
if(srcDoc.session_id === targetDoc.session_id) {
return {
source_last_seq: srcDoc.source_last_seq,
history: srcDoc.history || [];
};
}
var sourceHistory = srcDoc.history || [];
var targetHistory = tgtDoc.history || [];
return compareReplicationHistory(sourceHistory, targetHistory);
}
function compareReplicationHistory(sourceHistory, targetHistory) {
// the erlang loop via function arguments is not so easy to repeat in JS
// therefore, doing this as recursion
var S = sourceHistory[0]
var sourceRest = sourceHistory.slice(1);
var T = targetHistory[0];
var targetRest = targetHistory.slice(1);
if(!S || targetHistory.length === 0) {
return {
source_last_seq: LOWEST_SEQ,
history: [];
};
}
var sourceId = S.session_id;
if(hasSessionId(s, targetHistory)) {
return {
source_last_seq: S.recorded_seq,
history: sourceHistory
};
}
var targetId = T.session_id;
if(hasSessionId(targetId, sourceRest)) {
return {
source_last_seq: T.recorded_seq,
history: targetRest
};
}
return compareReplicationHistory(sourceRest, targetRest);
}
function hasSessionId(sessionId, history) {
var props = history[0];
var rest = history.slice(1);
if(!sessionId || history.length === 0) {
return false;
}
if(sessionId === props.session_id) {
return true;
}
return hasSessionId(sessionId, rest);
}
% those are the ported lines of code
% they come from here: https://github.com/apache/couchdb-couch-replicator/blob/master/src/couch_replicator.erl#L863-L906
compare_replication_logs(SrcDoc, TgtDoc) ->
#doc{body={RepRecProps}} = SrcDoc,
#doc{body={RepRecPropsTgt}} = TgtDoc,
case get_value(<<"session_id">>, RepRecProps) ==
get_value(<<"session_id">>, RepRecPropsTgt) of
true ->
% if the records have the same session id,
% then we have a valid replication history
OldSeqNum = get_value(<<"source_last_seq">>, RepRecProps, ?LOWEST_SEQ),
OldHistory = get_value(<<"history">>, RepRecProps, []),
{OldSeqNum, OldHistory};
false ->
SourceHistory = get_value(<<"history">>, RepRecProps, []),
TargetHistory = get_value(<<"history">>, RepRecPropsTgt, []),
couch_log:notice("Replication records differ. "
"Scanning histories to find a common ancestor.", []),
couch_log:debug("Record on source:~p~nRecord on target:~p~n",
[RepRecProps, RepRecPropsTgt]),
compare_rep_history(SourceHistory, TargetHistory)
end.
compare_rep_history(S, T) when S =:= [] orelse T =:= [] ->
couch_log:notice("no common ancestry -- performing full replication", []),
{?LOWEST_SEQ, []};
compare_rep_history([{S} | SourceRest], [{T} | TargetRest] = Target) ->
SourceId = get_value(<<"session_id">>, S),
case has_session_id(SourceId, Target) of
true ->
RecordSeqNum = get_value(<<"recorded_seq">>, S, ?LOWEST_SEQ),
couch_log:notice("found a common replication record with source_seq ~p",
[RecordSeqNum]),
{RecordSeqNum, SourceRest};
false ->
TargetId = get_value(<<"session_id">>, T),
case has_session_id(TargetId, SourceRest) of
true ->
RecordSeqNum = get_value(<<"recorded_seq">>, T, ?LOWEST_SEQ),
couch_log:notice("found a common replication record with source_seq ~p",
[RecordSeqNum]),
{RecordSeqNum, TargetRest};
false ->
compare_rep_history(SourceRest, TargetRest)
end
end.
has_session_id(_SessionId, []) ->
false;
has_session_id(SessionId, [{Props} | Rest]) ->
case get_value(<<"session_id">>, Props, nil) of
SessionId ->
true;
_Else ->
has_session_id(SessionId, Rest)
end.
@kocolosk
Copy link

kocolosk commented Jul 1, 2015

Typo on L33 but yes, this looks like a faithful JS implementation of the Erlang algorithm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment