Skip to content

Commit e0f7175

Browse files
azrBryan C. Mills
authored and
Bryan C. Mills
committed
design: add proposal doc for 33974
Make the `cmd/internal/lockedfile` Package public. Updates golang/go#33974 Change-Id: I2502fad153254d9ddb2bcc96ed6d8ef163940add GitHub-Last-Rev: d6be79e GitHub-Pull-Request: #21 Reviewed-on: https://go-review.googlesource.com/c/proposal/+/201277 Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 67a7911 commit e0f7175

File tree

1 file changed

+207
-0
lines changed

1 file changed

+207
-0
lines changed
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
# Proposal: make the internal [lockedfile](https://godoc.org/github.com/golang/go/src/cmd/go/internal/lockedfile/) package public
2+
3+
Author(s): [Adrien Delorme]
4+
5+
Last updated: 2019-10-15
6+
7+
Discussion at https://golang.org/issue/33974.
8+
9+
## Abstract
10+
11+
Move already existing code residing in
12+
`golang/go/src/cmd/go/internal/lockedfile` to `x/sync`.
13+
14+
## Background
15+
16+
A few open source Go projects are implementing file locking mechanisms but they
17+
do not seem to be maintained anymore:
18+
* https://github.com/gofrs/flock : This repo has lastly accepted PRs in March
19+
2019, so this implementation may be maintained and we could argue that the
20+
`lockedfile` package API is more ergonomic. Incompatibilities with AIX,
21+
Solaris and Illumos are preventing file locking on both projects, but it
22+
looks like the go team is addressing for `lockedfile`.
23+
* https://github.com/juju/fslock : Note that this implementation is both
24+
unmaintained and LGPL-licensed, so even folks who would like to use it might
25+
not be able to. Also not that this repo [was selected for removal in
26+
2017](https://github.com/juju/fslock/issues/4)
27+
28+
29+
As a result some major projects are doing
30+
their own version of it; ex:
31+
[terraform](https://github.com/hashicorp/terraform/blob/1ff9a540202b8c36e33db950374bbb4495737d8f/states/statemgr/filesystem_lock_unix.go),
32+
[boltdb](https://github.com/boltdb/bolt/search?q=flock&unscoped_q=flock). After
33+
some researches it seemed to us that the already existing and maintained
34+
[lockedfile](https://godoc.org/github.com/golang/go/src/cmd/go/internal/lockedfile/)
35+
package is the best 'open source' version.
36+
37+
File-locking interacts pretty deeply with the `os` package and the system call
38+
library in `x/sys`, so it makes sense for (a subset of) the same owners to
39+
consider the evolution of those packages together.
40+
We think it would benefit the mass to make such a package public: since it's
41+
already being part of the go code and therefore being maintained; it should be
42+
made public.
43+
44+
## Proposal
45+
46+
We propose to copy the golang/go/src/cmd/go/internal/lockedfile to `x/exp`. To
47+
make it public. Not changing any of the named types for now.
48+
49+
Exported names and comments as can be currently found in
50+
[07b4abd](https://github.com/golang/go/tree/07b4abd62e450f19c47266b3a526df49c01ba425/src/cmd/go/internal/lockedfile):
51+
52+
```
53+
// Package lockedfile creates and manipulates files whose contents should only
54+
// change atomically.
55+
package lockedfile
56+
57+
// A File is a locked *os.File.
58+
//
59+
// Closing the file releases the lock.
60+
//
61+
// If the program exits while a file is locked, the operating system releases
62+
// the lock but may not do so promptly: callers must ensure that all locked
63+
// files are closed before exiting.
64+
type File struct {
65+
// contains unexported fields
66+
}
67+
68+
// Create is like os.Create, but returns a write-locked file.
69+
// If the file already exists, it is truncated.
70+
func Create(name string) (*File, error)
71+
72+
// Edit creates the named file with mode 0666 (before umask),
73+
// but does not truncate existing contents.
74+
//
75+
// If Edit succeeds, methods on the returned File can be used for I/O.
76+
// The associated file descriptor has mode O_RDWR and the file is write-locked.
77+
func Edit(name string) (*File, error)
78+
79+
// Transform invokes t with the result of reading the named file, with its lock
80+
// still held.
81+
//
82+
// If t returns a nil error, Transform then writes the returned contents back to
83+
// the file, making a best effort to preserve existing contents on error.
84+
//
85+
// t must not modify the slice passed to it.
86+
func Transform(name string, t func([]byte) ([]byte, error)) (err error)
87+
88+
// Open is like os.Open, but returns a read-locked file.
89+
func Open(name string) (*File, error)
90+
91+
// OpenFile is like os.OpenFile, but returns a locked file.
92+
// If flag implies write access (ie: os.O_TRUNC, os.O_WRONLY or os.O_RDWR), the
93+
// file is write-locked; otherwise, it is read-locked.
94+
func OpenFile(name string, flag int, perm os.FileMode) (*File, error)
95+
96+
// Read reads up to len(b) bytes from the File.
97+
// It returns the number of bytes read and any error encountered.
98+
// At end of file, Read returns 0, io.EOF.
99+
//
100+
// File can be read-locked or write-locked.
101+
func (f *File) Read(b []byte) (n int, err error)
102+
103+
// ReadAt reads len(b) bytes from the File starting at byte offset off.
104+
// It returns the number of bytes read and the error, if any.
105+
// ReadAt always returns a non-nil error when n < len(b).
106+
// At end of file, that error is io.EOF.
107+
//
108+
// File can be read-locked or write-locked.
109+
func (f *File) ReadAt(b []byte, off int64) (n int, err error)
110+
111+
// Write writes len(b) bytes to the File.
112+
// It returns the number of bytes written and an error, if any.
113+
// Write returns a non-nil error when n != len(b).
114+
//
115+
// If File is not write-locked Write returns an error.
116+
func (f *File) Write(b []byte) (n int, err error)
117+
118+
// WriteAt writes len(b) bytes to the File starting at byte offset off.
119+
// It returns the number of bytes written and an error, if any.
120+
// WriteAt returns a non-nil error when n != len(b).
121+
//
122+
// If file was opened with the O_APPEND flag, WriteAt returns an error.
123+
//
124+
// If File is not write-locked WriteAt returns an error.
125+
func (f *File) WriteAt(b []byte, off int64) (n int, err error)
126+
127+
// Close unlocks and closes the underlying file.
128+
//
129+
// Close may be called multiple times; all calls after the first will return a
130+
// non-nil error.
131+
func (f *File) Close() error
132+
133+
// A Mutex provides mutual exclusion within and across processes by locking a
134+
// well-known file. Such a file generally guards some other part of the
135+
// filesystem: for example, a Mutex file in a directory might guard access to
136+
// the entire tree rooted in that directory.
137+
//
138+
// Mutex does not implement sync.Locker: unlike a sync.Mutex, a lockedfile.Mutex
139+
// can fail to lock (e.g. if there is a permission error in the filesystem).
140+
//
141+
// Like a sync.Mutex, a Mutex may be included as a field of a larger struct but
142+
// must not be copied after first use. The Path field must be set before first
143+
// use and must not be change thereafter.
144+
type Mutex struct {
145+
// Path to the well-known lock file. Must be non-empty.
146+
//
147+
// Path must not change on a locked mutex.
148+
Path string
149+
// contains filtered or unexported fields
150+
}
151+
152+
// MutexAt returns a new Mutex with Path set to the given non-empty path.
153+
func MutexAt(path string) *Mutex
154+
155+
// Lock attempts to lock the Mutex.
156+
//
157+
// If successful, Lock returns a non-nil unlock function: it is provided as a
158+
// return-value instead of a separate method to remind the caller to check the
159+
// accompanying error. (See https://golang.org/issue/20803.)
160+
func (mu *Mutex) Lock() (unlock func(), err error)
161+
162+
// String returns a string containing the path of the mutex.
163+
func (mu *Mutex) String() string
164+
```
165+
166+
## Rationale
167+
168+
* The `lockedfile.File` implements a subset of the `os.File` but with file
169+
locking protection.
170+
171+
* The `lockedfile.Mutex` does not implement `sync.Locker`: unlike a
172+
`sync.Mutex`, a `lockedfile.Mutex` can fail to lock (e.g. if there is a
173+
permission error in the filesystem).
174+
175+
* `lockedfile` adds an `Edit` and a `Transform` function; `Edit` is not
176+
currently part of the `file` package. Edit exists to make it easier to
177+
implement locked read-modify-write operation. `Transform` simplifies the act
178+
of reading and then writing to a locked file.
179+
180+
181+
* Making this package public will make it more used. A tiny surge of issues
182+
might come in the beginning; at the benefits of everyone. (Unless it's bug
183+
free !!).
184+
185+
* There exists a https://godoc.org/github.com/rogpeppe/go-internal package that
186+
exports a lot of internal packages from the go repo. But if go-internal
187+
became wildly popular; in order to have a bug fixed or a feature introduced
188+
in; a user would still need to open a PR on the go repo; then the author of
189+
go-internal would need to update the package.
190+
191+
## Compatibility
192+
193+
There are no retro-compatibility issues since this will be a code addition but
194+
ideally we don't want to maintain two copies of this package going forward, and
195+
we probably don't want to vendor `x/exp` into the `cmd` module.
196+
197+
198+
199+
Perhaps that implies that this should go in the `x/sys` or `x/sync` repo instead?
200+
201+
## Implementation
202+
203+
Adrien Delorme plans to do copy the exported types in the proposal section from
204+
`cmd/go/internal/lockedfile` to `x/sync`.
205+
206+
Adrien Delorme plans to change the references to the `lockedfile` package in
207+
`cmd`.

0 commit comments

Comments
 (0)