Skip to content

Commit 630e2a4

Browse files
riptlripatel-fd
authored andcommitted
accdb: add peek API
Add API to speculatively read account data
1 parent 4b9968d commit 630e2a4

File tree

6 files changed

+499
-2
lines changed

6 files changed

+499
-2
lines changed

src/flamenco/accdb/Local.mk

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ $(call add-hdrs,fd_accdb_admin.h)
33
$(call add-objs,fd_accdb_admin,fd_flamenco)
44

55
# User API
6-
$(call add-hdrs,fd_accdb_user.h)
6+
$(call add-hdrs,fd_accdb_user.h fd_accdb_sync.h)
77
$(call add-objs,fd_accdb_user,fd_flamenco)

src/flamenco/accdb/fd_accdb_ref.h

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#ifndef HEADER_fd_src_flamenco_accdb_fd_accdb_ref_h
2+
#define HEADER_fd_src_flamenco_accdb_fd_accdb_ref_h
3+
4+
/* fd_accdb_ref.h provides account database handle classes.
5+
6+
- accdb_ref is an opaque handle to an account database cache entry.
7+
- accdb_ro (extends accdb_ref) represents a read-only handle.
8+
- accdb_rw (extends accdb_ro) represents a read-write handle.
9+
10+
- accdb_guardr is a read-only account lock guard
11+
- accdb_guardw is an exclusive account lock guard
12+
- accdb_spec is an account speculative read guard
13+
14+
These APIs sit between the database layer (abstracts away backing
15+
stores and DB specifics) and the runtime layer (offer no runtime
16+
protections). */
17+
18+
#include "../fd_flamenco_base.h"
19+
#include "../../funk/fd_funk_rec.h"
20+
#include "../../funk/fd_funk_val.h"
21+
22+
/* fd_accdb_ref_t is an opaque account database handle. */
23+
24+
struct fd_accdb_ref {
25+
ulong rec_laddr;
26+
ulong meta_laddr;
27+
};
28+
typedef struct fd_accdb_ref fd_accdb_ref_t;
29+
30+
/* fd_accdb_ro_t is a readonly account database handle. */
31+
32+
union fd_accdb_ro {
33+
fd_accdb_ref_t ref[1];
34+
struct {
35+
fd_funk_rec_t const * rec;
36+
fd_account_meta_t const * meta;
37+
};
38+
};
39+
typedef union fd_accdb_ro fd_accdb_ro_t;
40+
41+
FD_PROTOTYPES_BEGIN
42+
43+
static inline void const *
44+
fd_accdb_ref_data_const( fd_accdb_ro_t const * ro ) {
45+
return (void *)( ro->meta+1 );
46+
}
47+
48+
static inline ulong
49+
fd_accdb_ref_data_sz( fd_accdb_ro_t const * ro ) {
50+
return ro->meta->dlen;
51+
}
52+
53+
static inline ulong
54+
fd_accdb_ref_lamports( fd_accdb_ro_t const * ro ) {
55+
return ro->meta->lamports;
56+
}
57+
58+
static inline void const *
59+
fd_accdb_ref_owner( fd_accdb_ro_t const * ro ) {
60+
return ro->meta->owner;
61+
}
62+
63+
static inline uint
64+
fd_accdb_ref_exec_bit( fd_accdb_ro_t const * ro ) {
65+
return !!ro->meta->executable;
66+
}
67+
68+
static inline ulong
69+
fd_accdb_ref_slot( fd_accdb_ro_t const * ro ) {
70+
return ro->meta->slot;
71+
}
72+
73+
// void
74+
// fd_accdb_ref_lthash( fd_accdb_ro_t const * ro,
75+
// void * lthash );
76+
77+
FD_PROTOTYPES_END
78+
79+
/* fd_accdb_rw_t is a writable database handle. Typically, writable
80+
handles are only available for invisible/in-prepartion records.
81+
In rare cases (e.g. when booting up), components may directly write
82+
to globally visible writable records. */
83+
84+
union fd_accdb_rw {
85+
fd_accdb_ref_t ref[1];
86+
fd_accdb_ro_t ro [1];
87+
struct {
88+
fd_funk_rec_t * rec;
89+
fd_account_meta_t * meta;
90+
};
91+
};
92+
typedef union fd_accdb_rw fd_accdb_rw_t;
93+
94+
FD_PROTOTYPES_BEGIN
95+
96+
// void
97+
// fd_accdb_ref_clear( fd_accdb_rw_t * rw );
98+
99+
static inline ulong
100+
fd_accdb_ref_data_max( fd_accdb_rw_t * rw ) {
101+
ulong data_max;
102+
if( FD_UNLIKELY( __builtin_usubl_overflow( rw->rec->val_max, sizeof(fd_account_meta_t), &data_max ) ) ) {
103+
FD_LOG_CRIT(( "invalid rec->val_max %lu for account at rec %p", (ulong)rw->rec->val_max, (void *)rw->rec ));
104+
}
105+
return data_max;
106+
}
107+
108+
static inline void *
109+
fd_accdb_ref_data( fd_accdb_rw_t * rw ) {
110+
return (void *)( rw->meta+1 );
111+
}
112+
113+
static inline void
114+
fd_accdb_ref_data_set( fd_accdb_rw_t * rw,
115+
void const * data,
116+
ulong data_sz ) {
117+
ulong data_max = fd_accdb_ref_data_max( rw );
118+
if( FD_UNLIKELY( data_sz>data_max ) ) {
119+
FD_LOG_CRIT(( "attempted to write %lu bytes into a rec %p with only %lu bytes of data space",
120+
data_sz, (void *)rw->rec, data_max ));
121+
}
122+
fd_memcpy( fd_accdb_ref_data( rw ), data, data_sz );
123+
rw->meta->dlen = (uint)data_sz;
124+
rw->rec->val_sz = (uint)( sizeof(fd_account_meta_t)+data_sz ) & (FD_FUNK_REC_VAL_MAX-1);
125+
}
126+
127+
FD_FN_UNUSED static void
128+
fd_accdb_ref_data_sz_set( fd_accdb_rw_t * rw,
129+
ulong data_sz ) {
130+
ulong prev_sz = rw->meta->dlen;
131+
if( data_sz>prev_sz ) {
132+
/* Increasing size, zero out tail */
133+
ulong data_max = fd_accdb_ref_data_max( rw );
134+
if( FD_UNLIKELY( data_sz>data_max ) ) {
135+
FD_LOG_CRIT(( "attempted to write %lu bytes into a rec %p with only %lu bytes of data space",
136+
data_sz, (void *)rw->rec, data_max ));
137+
}
138+
void * tail = (uchar *)fd_accdb_ref_data( rw ) + prev_sz;
139+
fd_memset( tail, 0, data_sz-prev_sz );
140+
}
141+
rw->meta->dlen = (uint)data_sz;
142+
rw->rec->val_sz = (uint)( sizeof(fd_account_meta_t)+data_sz ) & (FD_FUNK_REC_VAL_MAX-1);
143+
}
144+
145+
static inline void
146+
fd_accdb_ref_lamports_set( fd_accdb_rw_t * rw,
147+
ulong lamports ) {
148+
rw->meta->lamports = lamports;
149+
}
150+
151+
static inline void
152+
fd_accdb_ref_owner_set( fd_accdb_rw_t * rw,
153+
void const * owner ) {
154+
memcpy( rw->meta->owner, owner, 32UL );
155+
}
156+
157+
static inline void
158+
fd_accdb_ref_exec_bit_set( fd_accdb_rw_t * rw,
159+
uint exec_bit ) {
160+
rw->meta->executable = !!exec_bit;
161+
}
162+
163+
static inline void
164+
fd_accdb_ref_slot_set( fd_accdb_rw_t * rw,
165+
ulong slot ) {
166+
rw->meta->slot = slot;
167+
}
168+
169+
FD_PROTOTYPES_END
170+
171+
/* fd_accdb_guardr_t tracks a rwlock being held as read-only.
172+
Destroying this guard object detaches the caller's thread from the
173+
rwlock. */
174+
175+
struct fd_accbd_guardr {
176+
fd_rwlock_t * rwlock;
177+
};
178+
179+
typedef struct fd_accdb_guardr fd_accdb_guardr_t;
180+
181+
/* fd_accdb_guardw_t tracks an rwlock being held exclusively.
182+
Destroying this guard object detaches the caller's thread from the
183+
lock. */
184+
185+
struct fd_accdb_guardw {
186+
fd_rwlock_t * rwlock;
187+
};
188+
189+
typedef struct fd_accdb_guardw fd_accdb_guardw_t;
190+
191+
/* fd_accdb_spec_t tracks a speculative access to a shared resource.
192+
Destroying this guard object marks the end of a speculative access. */
193+
194+
struct fd_accdb_spec {
195+
fd_funk_rec_key_t * keyp; /* shared key */
196+
fd_funk_rec_key_t key; /* expected key */
197+
};
198+
199+
typedef struct fd_accdb_spec fd_accdb_spec_t;
200+
201+
/* fd_accdb_spec_test returns 1 if the shared resources has not been
202+
invalidated up until now. Returns 0 if the speculative access may
203+
have possibly seen a conflict (e.g. a torn read, a use-after-free,
204+
etc). */
205+
206+
static inline int
207+
fd_accdb_spec_test( fd_accdb_spec_t const * spec ) {
208+
fd_funk_rec_key_t key_found = FD_VOLATILE_CONST( *spec->keyp );
209+
return !!fd_funk_rec_key_eq( &key_found, &spec->key );
210+
}
211+
212+
/* fd_accdb_spec_drop marks the end of a speculative access. */
213+
214+
static inline void
215+
fd_accdb_spec_drop( fd_accdb_spec_t * spec ) {
216+
/* Speculative accesses do not need central synchronization, so no
217+
need to inform the holder of the resource of this drop. */
218+
(void)spec;
219+
}
220+
221+
#endif /* HEADER_fd_src_flamenco_accdb_fd_accdb_ref_h */

src/flamenco/accdb/fd_accdb_sync.h

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#ifndef HEADER_fd_src_flamenco_accdb_fd_accdb_sync_h
2+
#define HEADER_fd_src_flamenco_accdb_fd_accdb_sync_h
3+
4+
/* fd_accdb_sync.h provides synchronous blocking APIs for the account
5+
database. */
6+
7+
#include "fd_accdb_user.h"
8+
#include "fd_accdb_ref.h"
9+
10+
/* Speculative zero-copy read API *************************************/
11+
12+
/* fd_accdb_peek_t is an ephemeral lock-free read-only pointer to an
13+
account in database cache. */
14+
15+
struct fd_accdb_peek {
16+
fd_accdb_ro_t acc[1];
17+
fd_accdb_spec_t spec[1];
18+
};
19+
20+
typedef struct fd_accdb_peek fd_accdb_peek_t;
21+
22+
FD_PROTOTYPES_BEGIN
23+
24+
/* fd_accdb_peek_try starts a speculative read of an account. Queries
25+
the account database cache for the given address. On success,
26+
returns peek, which holds a speculative reference to an account. Use
27+
fd_accdb_peek_test to confirm whether peek is still valid.
28+
29+
Typical usage like:
30+
31+
fd_accdb_peek_t peek[1];
32+
if( !fd_accdb_peek( accdb, ... ) ) {
33+
... account not found ...
34+
return;
35+
}
36+
... speculatively process account ...
37+
if( fd_accdb_peek_test( peek )!=FD_ACCDB_SUCCESS ) {
38+
... data race detected ...
39+
return;
40+
}
41+
... happy path ... */
42+
43+
fd_accdb_peek_t *
44+
fd_accdb_peek( fd_accdb_user_t * accdb,
45+
fd_accdb_peek_t * peek,
46+
fd_funk_txn_xid_t const * xid,
47+
void const * address );
48+
49+
/* fd_accdb_peek_test verifies whether a previously taken peek still
50+
refers to valid account data. */
51+
52+
FD_FN_PURE static inline int
53+
fd_accdb_peek_test( fd_accdb_peek_t const * peek ) {
54+
return fd_accdb_spec_test( peek->spec );
55+
}
56+
57+
/* fd_accdb_peek_drop releases the caller's interest in the account. */
58+
59+
static inline void
60+
fd_accdb_peek_drop( fd_accdb_peek_t * peek ) {
61+
fd_accdb_spec_drop( peek->spec );
62+
}
63+
64+
FD_PROTOTYPES_END
65+
66+
#endif /* HEADER_fd_src_flamenco_accdb_fd_accdb_sync_h */

0 commit comments

Comments
 (0)