Skip to content

Commit 1f6733e

Browse files
committed
Better handle data loss in first chunk
When data is lost in the first chunk ever written subsequent inits would detect this in the index correctly but position the segment at eof which is incorrect. This fix addressed that by ensuring the empty log case is handled correctly.
1 parent 94450a4 commit 1f6733e

File tree

3 files changed

+85
-8
lines changed

3 files changed

+85
-8
lines changed

.github/workflows/rabbitmq-oci.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,10 @@ jobs:
104104
path: ${{ steps.resolve-artifact-path.outputs.ARTIFACT_PATH }}
105105

106106
- name: Set up Docker Buildx
107-
uses: docker/setup-buildx-action@v1
107+
uses: docker/setup-buildx-action@v3
108108

109109
- name: Cache Docker layers
110-
uses: actions/cache@v2
110+
uses: actions/cache@v3
111111
with:
112112
path: /tmp/.buildx-cache
113113
key: ${{ runner.os }}-${{ matrix.image_tag_suffix }}-buildx-${{ github.event.pull_request.head.sha || github.sha }}
@@ -125,7 +125,7 @@ jobs:
125125
126126
- name: Login to DockerHub
127127
if: steps.authorized.outputs.PUSH == 'true'
128-
uses: docker/login-action@v1
128+
uses: docker/login-action@v3
129129
with:
130130
username: ${{ secrets.DOCKERHUB_USERNAME }}
131131
password: ${{ secrets.DOCKERHUB_PASSWORD }}
@@ -160,7 +160,7 @@ jobs:
160160
echo "::set-output name=TAG_4::${TAG_4}"
161161
162162
- name: Build and push
163-
uses: docker/build-push-action@v2
163+
uses: docker/build-push-action@v5
164164
with:
165165
context: rabbitmq-server/packaging/docker-image
166166
pull: true

src/osiris_log.erl

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353
make_counter/1]).
5454

5555
-export([dump_init/1,
56+
dump_init_idx/1,
5657
dump_chunk/1,
58+
dump_index/1,
5759
dump_crc_check/1]).
5860
%% for testing
5961
-export([
@@ -597,10 +599,12 @@ init(#{dir := Dir,
597599
%% the empty log case
598600
{ok, SegFd} = open(Filename, ?FILE_OPTS_WRITE),
599601
{ok, IdxFd} = open(IdxFilename, ?FILE_OPTS_WRITE),
600-
%% TODO: do we potentially need to truncate the segment
601-
%% here too?
602-
{ok, _} = file:position(SegFd, eof),
603-
{ok, _} = file:position(IdxFd, eof),
602+
{ok, _} = file:position(SegFd, ?LOG_HEADER_SIZE),
603+
%% the segment could potentially have trailing data here so we'll
604+
%% do a truncate just in case. The index would have been truncated
605+
%% earlier
606+
ok = file:truncate(SegFd),
607+
{ok, _} = file:position(IdxFd, ?IDX_HEADER_SIZE),
604608
osiris_log_shared:set_first_chunk_id(Shared, DefaultNextOffset - 1),
605609
#?MODULE{cfg = Cfg,
606610
mode =
@@ -3029,6 +3033,29 @@ dump_init(File) ->
30293033
{ok, <<"OSIL", _V:4/binary>> } = file:read(Fd, ?LOG_HEADER_SIZE),
30303034
Fd.
30313035

3036+
dump_init_idx(File) ->
3037+
{ok, Fd} = file:open(File, [raw, binary, read]),
3038+
{ok, <<"OSII", _V:4/binary>> } = file:read(Fd, ?IDX_HEADER_SIZE),
3039+
Fd.
3040+
3041+
dump_index(Fd) ->
3042+
case file:read(Fd, ?INDEX_RECORD_SIZE_B) of
3043+
{ok,
3044+
<<ChunkId:64/unsigned,
3045+
Timestamp:64/signed,
3046+
Epoch:64/unsigned,
3047+
FilePos:32/unsigned,
3048+
ChType:8/unsigned>>} ->
3049+
#{chunk_id => ChunkId,
3050+
timestamp => Timestamp,
3051+
epoch => Epoch,
3052+
file_pos => FilePos,
3053+
type => ChType};
3054+
Err ->
3055+
Err
3056+
end.
3057+
3058+
30323059

30333060
dump_chunk(Fd) ->
30343061
{ok, Pos} = file:position(Fd, cur),

test/osiris_SUITE.erl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ all() ->
3131

3232
all_tests() ->
3333
[single_node_write,
34+
single_node_write_sub_batch_restart,
3435
single_node_uncorrelated_write,
3536
cluster_write_replication_plain,
3637
cluster_write_replication_tls,
@@ -178,6 +179,49 @@ single_node_write(Config) ->
178179
?assertEqual(42, osiris:fetch_writer_seq(Leader, Wid)),
179180
ok.
180181

182+
single_node_write_sub_batch_restart(Config) ->
183+
Name = ?config(cluster_name, Config),
184+
Dir = ?config(priv_dir, Config),
185+
Conf0 =
186+
#{name => Name,
187+
reference => Name,
188+
epoch => 1,
189+
leader_node => node(),
190+
replica_nodes => [],
191+
tracking_max_writers => 255,
192+
dir => Dir},
193+
SDir = filename:join(Dir, Name),
194+
{ok, #{leader_pid := Leader}} = osiris:start_cluster(Conf0),
195+
Entries = [simple(<<"abcdefghikjlmn">>) || _ <- lists:seq(1, 11)],
196+
Batch = {batch, 11, 0, iolist_size(Entries), Entries},
197+
ok = osiris:write(Leader, undefined, 42, Batch),
198+
receive
199+
{osiris_written, _Name, _WriterId, [42]} ->
200+
ok = osiris_writer:stop(Conf0),
201+
%% simulate data loss of first chunk, only header remains
202+
truncate(filename:join(SDir, "00000000000000000000.segment"), 56),
203+
{ok, Leader1} = osiris_writer:start(Conf0#{epoch => 3}),
204+
osiris_writer:read_tracking(Leader1),
205+
ok = osiris:write(Leader1, undefined, 43, Batch),
206+
receive
207+
{osiris_written, _, _, [43]} ->
208+
ok = osiris_writer:stop(Conf0),
209+
{ok, Leader2} = osiris_writer:start(Conf0#{epoch => 4}),
210+
%% read tracking to ensure write is actually running ok
211+
#{} = osiris_writer:read_tracking(Leader2),
212+
ok
213+
after 2000 ->
214+
flush(),
215+
exit(osiris_written_timeout)
216+
end,
217+
timer:sleep(1000),
218+
ok
219+
after 2000 ->
220+
flush(),
221+
exit(osiris_written_timeout)
222+
end,
223+
ok.
224+
181225
single_node_uncorrelated_write(Config) ->
182226
Name = ?config(cluster_name, Config),
183227
Conf0 =
@@ -2113,3 +2157,9 @@ wildcard(Wc) when is_list(Wc) ->
21132157
wildcard(Wc) ->
21142158
wildcard(unicode:characters_to_list(Wc)).
21152159

2160+
truncate(File, Sz) ->
2161+
{ok, Fd} = file:open(File, [raw, binary, read, write]),
2162+
{ok, _} = file:position(Fd, Sz),
2163+
ok = file:truncate(Fd),
2164+
ok = file:close(Fd),
2165+
ok.

0 commit comments

Comments
 (0)