From cb96305592ec7bdb3ddc5b026b26056d1028393f Mon Sep 17 00:00:00 2001 From: Maurice van Veen Date: Wed, 7 Jan 2026 18:17:20 +0100 Subject: [PATCH] [FIXED] Filestore can't open msg block after single truncated block Signed-off-by: Maurice van Veen --- server/filestore.go | 2 +- server/jetstream_test.go | 49 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/server/filestore.go b/server/filestore.go index 8f8d6180154..298410ed013 100644 --- a/server/filestore.go +++ b/server/filestore.go @@ -2345,7 +2345,7 @@ func (fs *fileStore) recoverMsgs() error { if fs.ld != nil { var emptyBlks []*msgBlock for _, mb := range fs.blks { - if mb.msgs == 0 && mb.rbytes == 0 { + if mb.msgs == 0 && mb.rbytes == 0 && mb != fs.lmb { emptyBlks = append(emptyBlks, mb) } } diff --git a/server/jetstream_test.go b/server/jetstream_test.go index 1b5ad2adbf1..f682fcb647e 100644 --- a/server/jetstream_test.go +++ b/server/jetstream_test.go @@ -22329,3 +22329,52 @@ func TestJetStreamServerEncryptionRecoveryWithoutStreamStateFile(t *testing.T) { }) } } + +func TestJetStreamFileStoreErrorOpeningBlockAfterTruncate(t *testing.T) { + storeDir := t.TempDir() + conf := createConfFile(t, []byte(fmt.Sprintf(` + listen: 127.0.0.1:-1 + jetstream: {store_dir: %q} + `, storeDir))) + + s, _ := RunServerWithConfig(conf) + defer s.Shutdown() + + nc, js := jsClientConnect(t, s) + defer nc.Close() + + _, err := js.AddStream(&nats.StreamConfig{ + Name: "TEST", + Subjects: []string{"foo"}, + }) + require_NoError(t, err) + + pubAck, err := js.Publish("foo", nil) + require_NoError(t, err) + require_Equal(t, pubAck.Sequence, 1) + + // Shut down the server and manually truncate the message blocks to be entirely empty, simulating data loss. + mset, err := s.globalAccount().lookupStream("TEST") + require_NoError(t, err) + fs := mset.store.(*fileStore) + blk := filepath.Join(fs.fcfg.StoreDir, msgDir, "1.blk") + index := filepath.Join(fs.fcfg.StoreDir, msgDir, streamStreamStateFile) + nc.Close() + s.Shutdown() + + // Truncate the block such that it isn't fully empty, but doesn't contain any messages. + require_NoError(t, os.Truncate(blk, 1)) + require_NoError(t, os.Remove(index)) + + // Restart the server and reconnect. + s, _ = RunServerWithConfig(conf) + defer s.Shutdown() + nc, js = jsClientConnect(t, s) + defer nc.Close() + + // Publish another message. Due to the simulated data loss, the stream sequence should continue + // counting after truncating the corrupted data. + pubAck, err = js.Publish("foo", nil) + require_NoError(t, err) + require_Equal(t, pubAck.Sequence, 1) +}