connection.go 10.6 KB
Newer Older
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
1 2
// Copyright (C) 2018-2019  Nexedi SA and Contributors.
//                          Kirill Smelkov <kirr@nexedi.com>
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
3
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
19 20 21 22

package zodb
// application-level database connection.

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
23 24
import (
	"context"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
25
	"fmt"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
26 27 28
	"sync"

	"lab.nexedi.com/kirr/go123/mem"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
29
	"lab.nexedi.com/kirr/go123/xerr"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
30
	"lab.nexedi.com/kirr/neo/go/transaction"
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
31 32 33
	"lab.nexedi.com/kirr/neo/go/zodb/internal/weak"
)

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
34
// Connection represents application-level view of a ZODB database.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
35 36
//
// The view is represented by IPersistent objects associated with the connection.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
37
// Connection changes are private and are isolated from changes in other Connections.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
38 39
// Connection's view corresponds to particular database state and is thus
// isolated from further database transactions.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
40 41 42
//
// Connection is safe to access from multiple goroutines simultaneously.
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
43 44
// Connection and objects obtained from it must be used by application only
// inside transaction where Connection was opened.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
45 46
//
// Use DB.Open to open a connection.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
47
type Connection struct {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
48
	db   *DB                     // Connection is part of this DB
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
49
	txn  transaction.Transaction // opened under this txn; nil after transaction ends.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
50
	at   Tid                     // current view of database; stable inside a transaction.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
51

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
52 53
	cache  LiveCache // cache of connection's in-RAM objects
	noPool bool      // connection is not returned to db.pool
54 55 56 57 58 59 60 61 62 63
}

// LiveCache keeps registry of live in-RAM objects for a Connection.
//
// It semantically consists of
//
//	{} oid -> obj
//
// but does not hold strong reference to cached objects.
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
64
// LiveCache is not safe to use from multiple goroutines simultaneously.
65
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
66
// Use .Lock() / .Unlock() to serialize access.
67 68
type LiveCache struct {
	// rationale for using weakref:
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
69 70 71 72 73 74 75 76 77 78
	//
	// on invalidations: we need to go oid -> obj and invalidate it.
	// -> Connection need to keep {} oid -> obj.
	// -> we can use that {} when loading a persistent Ref twice to get to the same object.
	//
	// however: if Connection keeps strong link to obj, just
	// obj.PDeactivate will not fully release obj if there are no
	// references to it from other objects:
	//
	//	- deactivate will release obj state (ok)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
79 80
	//	- but there will be still reference from connection `oid -> obj` map to this object,
	//	  which means the object won't be garbage-collected.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
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
	//
	// -> we can solve it by using "weak" pointers in the map.
	//
	// NOTE we cannot use regular map and arbitrarily manually "gc" entries
	// there periodically: since for an obj we don't know whether other
	// objects are referencing it, we can't just remove obj's oid from
	// the map - if we do so and there are other live objects that
	// reference obj, user code can still reach obj via those
	// references. On the other hand, if another, not yet loaded, object
	// also references obj and gets loaded, traversing reference from
	// that loaded object will load second copy of obj, thus breaking 1
	// object in db <-> 1 live object invariant:
	//
	//	A  →  B  →  C
	//	↓           |
	//      D <--------- - - -> D2 (wrong)
	//
	// - A activate
	// - D activate
	// - B activate
	// - D gc, A still keeps link on D
	// - C activate -> it needs to get to D, but D was removed from objtab
	//   -> new D2 is wrongly created
	//
	// that's why we have to depend on Go's GC to know whether there are
	// still live references left or not. And that in turn means finalizers
	// and thus weak references.
	//
	// some link on the subject:
	// https://groups.google.com/forum/#!topic/golang-nuts/PYWxjT2v6ps
	//
	// NOTE2 finalizers don't run on when they are attached to an object in cycle.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
113
	// Hopefully we don't have cycles with BTree/Bucket.
114 115

	sync.Mutex
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
116 117 118
	objtab map[Oid]*weak.Ref // oid -> weak.Ref(IPersistent)

	// hooks for application to influence live caching decisions.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
119
	control LiveCacheControl
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
120 121 122
}

// LiveCacheControl is the interface that allows applications to influence
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
123 124 125 126
// Connection's decisions with respect to Connection's LiveCache.
//
// See Connection.Cache and LiveCache.SetControl for how to install
// LiveCacheControl on a connection's live cache.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
127
type LiveCacheControl interface {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
128 129 130 131 132 133 134 135 136 137 138 139 140 141
	// PCacheClassify is called to classify an object and returns live
	// cache policy that should be used for this object.
	PCacheClassify(obj IPersistent) PCachePolicy
}

// PCachePolicy describes live caching policy for a persistent object.
//
// It is | combination of PCache* flags with 0 meaning "use default policy".
//
// See LiveCacheControl for how to apply a caching policy.
type PCachePolicy int

const (
	// keep object pinned into cache, even if in ghost state.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
142
	//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
143
	// This allows to rely on object being never evicted from live cache.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
144
	//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
145 146 147 148 149 150 151 152
	// Note: object's state can still be evicted and the object can go into
	// ghost state. Use PCacheKeepState to prevent such automatic eviction
	// until it is really needed.
	PCachePinObject PCachePolicy = 1 << iota

        // don't discard object state.
	//
	// Note: on invalidation, state of invalidated objects is discarded
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
153
	// unconditionally.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
154 155 156 157 158 159
	PCacheKeepState		// XXX PCachePolicy explicitly?

	// data access is non-temporal.
	//
	// Object state is used once and then won't be used for a long time.
	// There is no reason to preserve object state in cache.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
160
	// XXX Don't pollute ...
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
161 162
	PCacheNonTemporal	// XXX PCachePolicy ?
)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
163

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
164 165
// ----------------------------------------

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
166 167 168
// newConnection creates new Connection associated with db.
func newConnection(db *DB, at Tid) *Connection {
	return &Connection{
169 170 171 172 173
		db:    db,
		at:    at,
		cache: LiveCache{
			objtab: make(map[Oid]*weak.Ref),
		},
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
174 175 176
	}
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
177 178 179 180 181 182
// At returns database state corresponding to the connection.
func (conn *Connection) At() Tid {
	conn.checkLive("at")
	return conn.at
}

183 184 185 186 187
// Cache returns connection's cache of live objects.
func (conn *Connection) Cache() *LiveCache {
	return &conn.cache
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
188 189 190 191 192 193 194 195 196
// wrongClassError is the error cause returned when ZODB object's class is not what was expected.
type wrongClassError struct {
	want, have string
}

func (e *wrongClassError) Error() string {
	return fmt.Sprintf("wrong class: want %q; have %q", e.want, e.have)
}

197
// Get lookups object corresponding to oid in the cache.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
198
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
199
// If object is found, it is guaranteed to stay in live cache while the caller keeps reference to it.
200 201
func (cache *LiveCache) Get(oid Oid) IPersistent {
	wobj := cache.objtab[oid]
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
202
	var obj IPersistent
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
203 204
	if wobj != nil {
		if xobj := wobj.Get(); xobj != nil {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
205
			obj = xobj.(IPersistent)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
206 207
		}
	}
208 209 210
	return obj
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
211 212 213 214 215
// set sets objects corre ... XXX
func (cache *LiveCache) set(oid Oid, obj IPersistent) {
	cache.objtab[oid] = weak.NewRef(obj)
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
216 217 218 219 220 221 222 223 224 225
// SetControl installs c to handle cache decisions.
//
// Any previously installed cache control is uninstalled.
// Passing nil sets the cache to have no control installed at all.
//
// It is not safe to call SetControl simultaneously to other cache operations.
func (cache *LiveCache) SetControl(c LiveCacheControl) {
	cache.control = c
}

226 227 228 229 230
// get is like Get, but used when we already know object class.
//
// Use-case: in ZODB references are (pyclass, oid), so new ghost is created
// without further loading anything.
func (conn *Connection) get(class string, oid Oid) (IPersistent, error) {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
231
	checkClass := true
232 233
	conn.cache.Lock() // XXX -> rlock?
	obj := conn.cache.Get(oid)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
234
	if obj == nil {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
235
		obj = newGhost(class, oid, conn)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
236
		conn.cache.objtab[oid] = weak.NewRef(obj) // XXX -> conn.cache.set(oid, obj)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
237
		checkClass = false
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
238
	}
239
	conn.cache.Unlock()
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
240 241

	if checkClass {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
242
		if cls := ClassOf(obj); class != cls {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
243 244 245
			var err error = &wrongClassError{class, cls}
			xerr.Contextf(&err, "get %s", Xid{conn.at, oid})
			return nil, err
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
246 247 248
		}
	}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
249
	return obj, nil
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
250 251
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
252 253 254 255 256
// Get returns in-RAM object corresponding to specified ZODB object according to current database view.
//
// If there is already in-RAM object that corresponds to oid, that in-RAM object is returned.
// Otherwise new in-RAM object is created and filled with object's class loaded from the database.
//
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
257
// The scope of the object returned is the Connection.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
258 259 260
//
// The object's data is not necessarily loaded after Get returns. Use
// PActivate to make sure the object is fully loaded.
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
261
func (conn *Connection) Get(ctx context.Context, oid Oid) (_ IPersistent, err error) {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
262
	conn.checkTxnCtx(ctx, "Get")
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
263
	defer xerr.Contextf(&err, "Get %s", oid)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
264

265 266 267
	conn.cache.Lock() // XXX -> rlock?
	obj := conn.cache.Get(oid)
	conn.cache.Unlock()
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
268

269 270 271
	// object was already there in cache.
	if obj != nil {
		return obj, nil
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
272 273
	}

274
	// object is not in cache - raw load it, get its class -> get(pyclass, oid)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
275
	// XXX "py always" hardcoded
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
276
	class, pystate, serial, err := conn.loadpy(ctx, oid)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
277
	if err != nil {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
278
		xerr.Contextf(&err, "Get %s", Xid{conn.at, oid})
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
279
		return nil, err
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
280 281
	}

282
	obj, err = conn.get(class, oid)
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
283
	if err != nil {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
284
		return nil, err
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
285 286 287 288 289 290 291 292 293 294
	}

	// XXX we are dropping just loaded pystate. Usually Get should be used
	// to only load root object, so maybe that is ok.
	//
	// TODO -> use (pystate, serial) to activate.
	_, _ = pystate, serial
	return obj, nil
}

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
295 296
// load loads object specified by oid.
func (conn *Connection) load(ctx context.Context, oid Oid) (_ *mem.Buf, serial Tid, _ error) {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
297
	conn.checkTxnCtx(ctx, "load")
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
298
	return conn.db.stor.Load(ctx, Xid{Oid: oid, At: conn.at})
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
299
}
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
300 301 302

// ----------------------------------------

Kirill Smelkov's avatar
.  
Kirill Smelkov committed
303 304 305 306 307 308 309 310
// checkTxnCtx asserts that current transaction is the same as conn.txn .
func (conn *Connection) checkTxnCtx(ctx context.Context, who string) {
	conn.checkTxn(transaction.Current(ctx), who)
}

// checkTxn asserts that specified "current" transaction is the same as conn.txn .
func (conn *Connection) checkTxn(txn transaction.Transaction, who string) {
	if txn != conn.txn {
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
311 312 313 314 315 316 317 318 319
		panic("connection: " + who + ": current transaction is different from connection transaction")
	}
}

// checkLive asserts that the connection is alive - the transaction under which
// it has been opened is not yet complete.
func (conn *Connection) checkLive(who string) {
	if conn.txn == nil {
		panic("connection: " + who + ": connection is not live")
Kirill Smelkov's avatar
.  
Kirill Smelkov committed
320 321
	}
}