Skip to content

Commit bbceac1

Browse files
authored
Merge pull request #110 from pjbgf/perf
memfs: Add thread-safety to Memory
2 parents b05d1c3 + 7c5ae60 commit bbceac1

File tree

13 files changed

+661
-285
lines changed

13 files changed

+661
-285
lines changed

.github/workflows/test.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ name: Test
22

33
on:
44
push:
5-
branches: [ "master", "main" ]
65
pull_request:
76

87
permissions: {}

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ $(GOLANGCI):
1414

1515
.PHONY: test
1616
test:
17-
$(GOTEST) -race ./...
17+
$(GOTEST) -race -timeout 300s ./...
1818

1919
test-coverage:
2020
echo "" > $(COVERAGE_REPORT); \

fs.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
)
99

1010
var (
11-
ErrReadOnly = errors.New("read-only filesystem")
12-
ErrNotSupported = errors.New("feature not supported")
13-
ErrCrossedBoundary = errors.New("chroot boundary crossed")
11+
ErrReadOnly = errors.New("read-only filesystem")
12+
ErrNotSupported = errors.New("feature not supported")
13+
ErrCrossedBoundary = errors.New("chroot boundary crossed")
14+
ErrBaseDirCannotBeRemoved = errors.New("base dir cannot be removed")
15+
ErrBaseDirCannotBeRenamed = errors.New("base dir cannot be renamed")
1416
)
1517

1618
// Capability holds the supported features of a billy filesystem. This does

memfs/file.go

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
package memfs
2+
3+
import (
4+
"errors"
5+
"io"
6+
"io/fs"
7+
"os"
8+
"sync"
9+
"time"
10+
11+
"github.com/go-git/go-billy/v6"
12+
)
13+
14+
type file struct {
15+
name string
16+
content *content
17+
position int64
18+
flag int
19+
mode os.FileMode
20+
modTime time.Time
21+
22+
isClosed bool
23+
}
24+
25+
func (f *file) Name() string {
26+
return f.name
27+
}
28+
29+
func (f *file) Read(b []byte) (int, error) {
30+
n, err := f.ReadAt(b, f.position)
31+
f.position += int64(n)
32+
33+
if errors.Is(err, io.EOF) && n != 0 {
34+
err = nil
35+
}
36+
37+
return n, err
38+
}
39+
40+
func (f *file) ReadAt(b []byte, off int64) (int, error) {
41+
if f.isClosed {
42+
return 0, os.ErrClosed
43+
}
44+
45+
if !isReadAndWrite(f.flag) && !isReadOnly(f.flag) {
46+
return 0, errors.New("read not supported")
47+
}
48+
49+
n, err := f.content.ReadAt(b, off)
50+
51+
return n, err
52+
}
53+
54+
func (f *file) Seek(offset int64, whence int) (int64, error) {
55+
if f.isClosed {
56+
return 0, os.ErrClosed
57+
}
58+
59+
switch whence {
60+
case io.SeekCurrent:
61+
f.position += offset
62+
case io.SeekStart:
63+
f.position = offset
64+
case io.SeekEnd:
65+
f.position = int64(f.content.Len()) + offset
66+
}
67+
68+
return f.position, nil
69+
}
70+
71+
func (f *file) Write(p []byte) (int, error) {
72+
return f.WriteAt(p, f.position)
73+
}
74+
75+
func (f *file) WriteAt(p []byte, off int64) (int, error) {
76+
if f.isClosed {
77+
return 0, os.ErrClosed
78+
}
79+
80+
if !isReadAndWrite(f.flag) && !isWriteOnly(f.flag) {
81+
return 0, errors.New("write not supported")
82+
}
83+
84+
f.modTime = time.Now()
85+
n, err := f.content.WriteAt(p, off)
86+
f.position = off + int64(n)
87+
88+
return n, err
89+
}
90+
91+
func (f *file) Close() error {
92+
if f.isClosed {
93+
return os.ErrClosed
94+
}
95+
96+
f.isClosed = true
97+
return nil
98+
}
99+
100+
func (f *file) Truncate(size int64) error {
101+
if size < int64(len(f.content.bytes)) {
102+
f.content.bytes = f.content.bytes[:size]
103+
} else if more := int(size) - len(f.content.bytes); more > 0 {
104+
f.content.bytes = append(f.content.bytes, make([]byte, more)...)
105+
}
106+
107+
return nil
108+
}
109+
110+
func (f *file) Duplicate(filename string, mode fs.FileMode, flag int) billy.File {
111+
n := &file{
112+
name: filename,
113+
content: f.content,
114+
mode: mode,
115+
flag: flag,
116+
modTime: f.modTime,
117+
}
118+
119+
if isTruncate(flag) {
120+
n.content.Truncate()
121+
}
122+
123+
if isAppend(flag) {
124+
n.position = int64(n.content.Len())
125+
}
126+
127+
return n
128+
}
129+
130+
func (f *file) Stat() (os.FileInfo, error) {
131+
return &fileInfo{
132+
name: f.Name(),
133+
mode: f.mode,
134+
size: f.content.Len(),
135+
modTime: f.modTime,
136+
}, nil
137+
}
138+
139+
// Lock is a no-op in memfs.
140+
func (f *file) Lock() error {
141+
return nil
142+
}
143+
144+
// Unlock is a no-op in memfs.
145+
func (f *file) Unlock() error {
146+
return nil
147+
}
148+
149+
type fileInfo struct {
150+
name string
151+
size int
152+
mode os.FileMode
153+
modTime time.Time
154+
}
155+
156+
func (fi *fileInfo) Name() string {
157+
return fi.name
158+
}
159+
160+
func (fi *fileInfo) Size() int64 {
161+
return int64(fi.size)
162+
}
163+
164+
func (fi *fileInfo) Mode() fs.FileMode {
165+
return fi.mode
166+
}
167+
168+
func (fi *fileInfo) ModTime() time.Time {
169+
return fi.modTime
170+
}
171+
172+
func (fi *fileInfo) IsDir() bool {
173+
return fi.mode.IsDir()
174+
}
175+
176+
func (*fileInfo) Sys() interface{} {
177+
return nil
178+
}
179+
180+
type content struct {
181+
name string
182+
bytes []byte
183+
184+
m sync.RWMutex
185+
}
186+
187+
func (c *content) WriteAt(p []byte, off int64) (int, error) {
188+
if off < 0 {
189+
return 0, &os.PathError{
190+
Op: "writeat",
191+
Path: c.name,
192+
Err: errors.New("negative offset"),
193+
}
194+
}
195+
196+
c.m.Lock()
197+
prev := len(c.bytes)
198+
199+
diff := int(off) - prev
200+
if diff > 0 {
201+
c.bytes = append(c.bytes, make([]byte, diff)...)
202+
}
203+
204+
c.bytes = append(c.bytes[:off], p...)
205+
if len(c.bytes) < prev {
206+
c.bytes = c.bytes[:prev]
207+
}
208+
c.m.Unlock()
209+
210+
return len(p), nil
211+
}
212+
213+
func (c *content) ReadAt(b []byte, off int64) (n int, err error) {
214+
if off < 0 {
215+
return 0, &os.PathError{
216+
Op: "readat",
217+
Path: c.name,
218+
Err: errors.New("negative offset"),
219+
}
220+
}
221+
222+
c.m.RLock()
223+
size := int64(len(c.bytes))
224+
if off >= size {
225+
c.m.RUnlock()
226+
return 0, io.EOF
227+
}
228+
229+
l := int64(len(b))
230+
if off+l > size {
231+
l = size - off
232+
}
233+
234+
btr := c.bytes[off : off+l]
235+
n = copy(b, btr)
236+
237+
if len(btr) < len(b) {
238+
err = io.EOF
239+
}
240+
c.m.RUnlock()
241+
242+
return
243+
}

0 commit comments

Comments
 (0)