-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathoptions.go
209 lines (183 loc) · 6.02 KB
/
options.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package kv
import (
"crypto/sha1"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"time"
"github.com/cznic/exp/lldb"
)
const (
// BeginUpdate/EndUpdate/Rollback will be no-ops. All operations
// updating a DB will be written immediately including partial updates
// during operation's progress. If any update fails, the DB can become
// unusable. The same applies to DB crashes and/or any other non clean
// DB shutdown.
_ACIDNone = iota
// Enable transactions. BeginUpdate/EndUpdate/Rollback will be
// effective. All operations on the DB will be automatically performed
// within a transaction. Operations will thus either succeed completely
// or have no effect at all - they will be rollbacked in case of any
// error. If any update fails the DB will not be corrupted. DB crashes
// and/or any other non clean DB shutdown may still render the DB
// unusable.
_ACIDTransactions
// Enable durability. Same as ACIDTransactions plus enables 2PC and
// WAL. Updates to the DB will be first made permanent in a WAL and
// only after that reflected in the DB. A DB will automatically recover
// from crashes and/or any other non clean DB shutdown. Only last
// uncommited transaction (transaction in progress ATM of a crash) can
// get lost.
//
// NOTE: Options.GracePeriod may extend the span of a single
// transaction to a batch of multiple transactions.
//
// NOTE2: Non zero GracePeriod requires GOMAXPROCS > 1 to work. Dbm
// checks GOMAXPROCS in such case and if the value is 1 it
// automatically sets GOMAXPROCS = 2.
_ACIDFull
)
// Options are passed to the DB create/open functions to amend the behavior of
// those functions. The compatibility promise is the same as of struct types in
// the Go standard library - introducing changes can be made only by adding new
// exported fields, which is backward compatible as long as client code uses
// field names to assign values of imported struct types literals.
type Options struct {
// Compare compares x and y. Compare may be nil, then bytes.Compare is
// used instead.
//
// Compare returns:
//
// -1 if x < y
// 0 if x == y
// +1 if x > y
Compare func(x, y []byte) int
// Locker specifies a function to lock a named file.
// On success it returns an io.Closer to release the lock.
// If nil, a default implementation is used.
Locker func(name string) (io.Closer, error)
// See the ACID* constants documentation.
_ACID int
// The write ahead log pathname. Applicable iff ACID == ACIDFull. May
// be left empty in which case an unspecified pathname will be chosen,
// which is computed from the DB name and which will be in the same
// directory as the DB. Moving or renaming the DB while it is shut down
// will break it's connection to the automatically computed name.
// Moving both the files (the DB and the WAL) into another directory
// with no renaming is safe.
//
// On opening an existing DB the WAL file must exist if it should be
// used. If it is of zero size then a clean shutdown of the DB is
// assumed, otherwise an automatic DB recovery is performed.
//
// On creating a new DB the WAL file must not exist. It's not safe to
// write to a WAL file as it may contain unprocessed DB recovery data.
_WAL string
// Time to collect transactions before committing them into the WAL.
// Applicable iff ACID == ACIDFull. All updates are held in memory
// during the grace period so it should not be more than few seconds at
// most.
//
// Recommended value for GracePeriod is 1 second.
//
// NOTE: Using small GracePeriod values will make DB updates very slow.
// Zero GracePeriod will make every single update a separate 2PC/WAL
// transaction. Values smaller than about 100-200 milliseconds
// (particularly for mechanical, rotational HDs) are not recommended
// and they may not be always honored.
_GracePeriod time.Duration
wal *os.File
lock io.Closer
noClone bool // test hook
}
func (o *Options) locker(dbname string) (io.Closer, error) {
if o == nil || o.Locker == nil {
return defaultLocker(dbname)
}
return o.Locker(dbname)
}
func (o *Options) clone() *Options {
if o.noClone {
return o
}
return &Options{Compare: o.Compare, Locker: o.Locker}
}
func (o *Options) check(dbname string, new, lock bool) (err error) {
if lock {
if o.lock, err = o.locker(dbname); err != nil {
return
}
}
switch o._ACID {
default:
panic("internal error")
case _ACIDTransactions:
case _ACIDFull:
o._GracePeriod = time.Second
o._WAL = o.walName(dbname, o._WAL)
switch new {
case true:
if o.wal, err = os.OpenFile(o._WAL, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666); err != nil {
if os.IsExist(err) {
err = fmt.Errorf("cannot create DB %q: WAL file %q exists", dbname, o._WAL)
}
return
}
case false:
if o.wal, err = os.OpenFile(o._WAL, os.O_RDWR, 0666); err != nil {
if os.IsNotExist(err) {
err = fmt.Errorf("cannot open DB %q: WAL file %q doesn't exist", dbname, o._WAL)
}
return
}
}
}
return
}
func (o *Options) walName(dbname, wal string) (r string) {
if wal != "" {
return filepath.Clean(wal)
}
base := filepath.Base(filepath.Clean(dbname))
h := sha1.New()
io.WriteString(h, base)
return filepath.Join(filepath.Dir(dbname), fmt.Sprintf(".%x", h.Sum(nil)))
}
func (o *Options) acidFiler(db *DB, f lldb.Filer) (r lldb.Filer, err error) {
switch o._ACID {
default:
panic("internal error")
case _ACIDTransactions:
var rf *lldb.RollbackFiler
if rf, err = lldb.NewRollbackFiler(
f,
func(sz int64) error {
return f.Truncate(sz)
},
f,
); err != nil {
return
}
r = rf
case _ACIDFull:
if r, err = lldb.NewACIDFiler(f, o.wal); err != nil {
return
}
db.acidState = stIdle
db.gracePeriod = o._GracePeriod
if o._GracePeriod == 0 {
panic("internal error")
}
// Ensure GOMAXPROCS > 1, required for ACID FSM
if n := runtime.GOMAXPROCS(0); n > 1 {
return
}
runtime.GOMAXPROCS(2)
}
return
}