summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul J. Davis <paul.joseph.davis@gmail.com>2017-07-12 15:50:17 -0500
committerPaul J. Davis <paul.joseph.davis@gmail.com>2017-07-13 10:45:58 -0500
commit764168c5b03f17b7adfa5db9a3b7341b01281f44 (patch)
treeb0da1bdb51779287bc5ade0b0ce0c7cf6aa77181
parent94f2907ea2d75a08f5dd8dae2edbfc6eea5c6bd7 (diff)
downloadcouchdb-764168c5b03f17b7adfa5db9a3b7341b01281f44.tar.gz
Prevent a terrible race condition
Looking into #649 I realized there's a pretty terrible race condition if an index is compacted quickly followed by an index update. Since we don't check the index updater message it would be possible for us to swap out a compaction change, followed by immediately resetting to the new state from the index updater. This would be bad as we'd possibly end up with a situation where our long lived index would be operating on a file that no longer existed on disk.
-rw-r--r--src/couch_index/src/couch_index.erl49
1 files changed, 31 insertions, 18 deletions
diff --git a/src/couch_index/src/couch_index.erl b/src/couch_index/src/couch_index.erl
index 9da928dac..604d5038c 100644
--- a/src/couch_index/src/couch_index.erl
+++ b/src/couch_index/src/couch_index.erl
@@ -230,24 +230,37 @@ handle_cast({new_state, NewIdxState}, State) ->
mod=Mod,
idx_state=OldIdxState
} = State,
- assert_signature_match(Mod, OldIdxState, NewIdxState),
- CurrSeq = Mod:get(update_seq, NewIdxState),
- Args = [
- Mod:get(db_name, NewIdxState),
- Mod:get(idx_name, NewIdxState),
- CurrSeq
- ],
- couch_log:debug("Updated index for db: ~s idx: ~s seq: ~B", Args),
- Rest = send_replies(State#st.waiters, CurrSeq, NewIdxState),
- case State#st.committed of
- true -> erlang:send_after(commit_delay(), self(), commit);
- false -> ok
- end,
- {noreply, State#st{
- idx_state=NewIdxState,
- waiters=Rest,
- committed=false
- }};
+ OldFd = Mod:get(fd, OldIdxState),
+ NewFd = Mod:get(fd, NewIdxState),
+ case NewFd == OldFd of
+ true ->
+ assert_signature_match(Mod, OldIdxState, NewIdxState),
+ CurrSeq = Mod:get(update_seq, NewIdxState),
+ Args = [
+ Mod:get(db_name, NewIdxState),
+ Mod:get(idx_name, NewIdxState),
+ CurrSeq
+ ],
+ couch_log:debug("Updated index for db: ~s idx: ~s seq: ~B", Args),
+ Rest = send_replies(State#st.waiters, CurrSeq, NewIdxState),
+ case State#st.committed of
+ true -> erlang:send_after(commit_delay(), self(), commit);
+ false -> ok
+ end,
+ {noreply, State#st{
+ idx_state=NewIdxState,
+ waiters=Rest,
+ committed=false
+ }};
+ false ->
+ Fmt = "Ignoring update from old indexer for db: ~s idx: ~s",
+ Args = [
+ Mod:get(db_name, NewIdxState),
+ Mod:get(idx_name, NewIdxState)
+ ],
+ couch_log:warning(Fmt, Args),
+ {noreply, State}
+ end;
handle_cast({update_error, Error}, State) ->
send_all(State#st.waiters, Error),
{noreply, State#st{waiters=[]}};