Skip to content

Commit 7b8decb

Browse files
committed
fix: add closed check, expose storage.ErrClosed
1 parent e9e60af commit 7b8decb

File tree

3 files changed

+65
-11
lines changed

3 files changed

+65
-11
lines changed

v2/storage/deferred/deferredcarwriter.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ var _ io.Closer = (*DeferredCarWriter)(nil)
2929
// DeferredCarWriter is threadsafe, and can be used concurrently.
3030
// Closing the writer will close, but not delete, the underlying file.
3131
//
32+
// DeferredCarWriter only implements the storage.WritableStorage interface and
33+
// is not intended as a general purpose storage implementation. It only supports
34+
// storage Put() and Get() operations.
35+
//
3236
// This utility is useful for cases where a CAR will be streamed but an error
3337
// may occur before any content is written. In this case, the CAR file will not
3438
// be created, and the output stream will not be written to. In the case of an
@@ -45,11 +49,12 @@ type DeferredCarWriter struct {
4549
outPath string
4650
outStream io.Writer
4751

48-
lk sync.Mutex
49-
f *os.File
50-
w carstorage.WritableCar
51-
putCb []putCb
52-
opts []carv2.Option
52+
lk sync.Mutex
53+
f *os.File
54+
closed bool
55+
w carstorage.WritableCar
56+
putCb []putCb
57+
opts []carv2.Option
5358
}
5459

5560
// NewDeferredCarWriterForPath creates a DeferredCarWriter that will write to a
@@ -89,6 +94,10 @@ func (dcw *DeferredCarWriter) Has(ctx context.Context, key string) (bool, error)
8994
dcw.lk.Lock()
9095
defer dcw.lk.Unlock()
9196

97+
if dcw.closed {
98+
return false, carstorage.ErrClosed
99+
}
100+
92101
if dcw.w == nil { // shortcut, haven't written anything, don't even initialise
93102
return false, nil
94103
}
@@ -107,6 +116,10 @@ func (dcw *DeferredCarWriter) Put(ctx context.Context, key string, content []byt
107116
dcw.lk.Lock()
108117
defer dcw.lk.Unlock()
109118

119+
if dcw.closed {
120+
return carstorage.ErrClosed
121+
}
122+
110123
if dcw.putCb != nil {
111124
// call all callbacks, remove those that were only needed once
112125
for i := 0; i < len(dcw.putCb); i++ {
@@ -150,11 +163,18 @@ func (dcw *DeferredCarWriter) writer() (carstorage.WritableCar, error) {
150163
}
151164

152165
// Close closes the underlying file, if one was created.
153-
func (dcw *DeferredCarWriter) Close() error {
166+
func (dcw *DeferredCarWriter) Close() (err error) {
154167
dcw.lk.Lock()
155168
defer dcw.lk.Unlock()
156169

157-
err := dcw.w.Finalize()
170+
if dcw.closed {
171+
return carstorage.ErrClosed
172+
}
173+
dcw.closed = true
174+
175+
if dcw.w != nil {
176+
err = dcw.w.Finalize()
177+
}
158178

159179
if dcw.f != nil {
160180
defer func() { dcw.f = nil }()
@@ -163,6 +183,7 @@ func (dcw *DeferredCarWriter) Close() error {
163183
err = err2
164184
}
165185
}
186+
166187
return err
167188
}
168189

v2/storage/deferred/deferredcarwriter_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/ipfs/go-cid"
1414
carv2 "github.com/ipld/go-car/v2"
15+
"github.com/ipld/go-car/v2/storage"
1516
deferred "github.com/ipld/go-car/v2/storage/deferred"
1617
mh "github.com/multiformats/go-multihash"
1718
"github.com/stretchr/testify/require"
@@ -218,6 +219,38 @@ func TestDeferredCarWriterPutCb(t *testing.T) {
218219
require.Equal(t, 1, pc3)
219220
}
220221

222+
func TestDeferredCarWriterWriteAfterClose(t *testing.T) {
223+
req := require.New(t)
224+
225+
ctx := context.Background()
226+
testCid1, testData1 := randBlock()
227+
testCid2, testData2 := randBlock()
228+
229+
var buf bytes.Buffer
230+
cw := deferred.NewDeferredCarWriterForStream(&buf, []cid.Cid{testCid1})
231+
// no writes
232+
req.NoError(cw.Close())
233+
234+
req.ErrorIs(cw.Put(ctx, testCid1.KeyString(), testData1), storage.ErrClosed)
235+
_, err := cw.Has(ctx, testCid1.KeyString())
236+
req.ErrorIs(err, storage.ErrClosed)
237+
req.ErrorIs(cw.Close(), storage.ErrClosed)
238+
239+
// with writes
240+
241+
buf = bytes.Buffer{}
242+
cw = deferred.NewDeferredCarWriterForStream(&buf, []cid.Cid{testCid1})
243+
244+
req.NoError(cw.Put(ctx, testCid1.KeyString(), testData1))
245+
req.NoError(cw.Put(ctx, testCid2.KeyString(), testData2))
246+
req.NoError(cw.Close())
247+
248+
req.ErrorIs(cw.Put(ctx, testCid1.KeyString(), testData1), storage.ErrClosed)
249+
_, err = cw.Has(ctx, testCid1.KeyString())
250+
req.ErrorIs(err, storage.ErrClosed)
251+
req.ErrorIs(cw.Close(), storage.ErrClosed)
252+
}
253+
221254
func randBlock() (cid.Cid, []byte) {
222255
data := make([]byte, 1024)
223256
rngLk.Lock()

v2/storage/storage.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
ipldstorage "github.com/ipld/go-ipld-prime/storage"
1919
)
2020

21-
var errClosed = errors.New("cannot use a CARv2 storage after closing")
21+
var ErrClosed = errors.New("cannot use a CAR storage after closing")
2222

2323
type ReaderAtWriterAt interface {
2424
io.ReaderAt
@@ -314,7 +314,7 @@ func (sc *StorageCar) Put(ctx context.Context, keyStr string, data []byte) error
314314
defer sc.mu.Unlock()
315315

316316
if sc.closed {
317-
return errClosed
317+
return ErrClosed
318318
}
319319

320320
idx, ok := sc.idx.(*index.InsertionIndex)
@@ -361,7 +361,7 @@ func (sc *StorageCar) Has(ctx context.Context, keyStr string) (bool, error) {
361361
defer sc.mu.RUnlock()
362362

363363
if sc.closed {
364-
return false, errClosed
364+
return false, ErrClosed
365365
}
366366

367367
if idx, ok := sc.idx.(*index.InsertionIndex); ok && sc.writer != nil {
@@ -443,7 +443,7 @@ func (sc *StorageCar) GetStream(ctx context.Context, keyStr string) (io.ReadClos
443443
defer sc.mu.RUnlock()
444444

445445
if sc.closed {
446-
return nil, errClosed
446+
return nil, ErrClosed
447447
}
448448

449449
_, offset, size, err := store.FindCid(

0 commit comments

Comments
 (0)