Line data Source code
1 : #ifndef HEADER_fd_src_flamenco_progcache_fd_progcache_user_h 2 : #define HEADER_fd_src_flamenco_progcache_fd_progcache_user_h 3 : 4 : /* fd_progcache_user.h provides an API for managing a cache of loaded 5 : Solana on-chain program. 6 : 7 : ### Background 8 : 9 : Solana on-chain programs are rarely updated but frequently executed. 10 : Before a program can be executed, it must be loaded and verified, 11 : which is costly. 12 : 13 : ### Fork management 14 : 15 : The program cache is fork-aware (using funk transactions). Txn-level 16 : operations take an exclusive lock over the cache (record ops are 17 : stalled indefinitely until the txn completes). 18 : 19 : ### Cache entry 20 : 21 : Each Solana program can have a number of program cache entries 22 : (typically only zero or one, in rare cases where the program content 23 : differs across forks multiple). 24 : 25 : A cache entry consists of a funk_rec object (from a preallocated 26 : object pool), and a variable-sized fd_progcache_entry struct 27 : (from an fd_alloc heap). 28 : 29 : ### Cache fill policy 30 : 31 : fd_progcache is lazily filled on reads, and eagerly invalidated 32 : if underlying programs are written to. 33 : 34 : ### Cache evict policy 35 : 36 : Cache eviction (i.e. force removal of potentially useful records) 37 : happens on fill. Specifically, cache eviction is triggered when a 38 : cache fill fails to allocate from the wksp (fd_alloc) heap. 39 : 40 : fd_progcache further has a concept of "generations" (gen). Each 41 : cache fill operation specifies a 'gen' number. Only entries with a 42 : lower 'gen' number may get evicted. 43 : 44 : ### Garbage collect policy 45 : 46 : fd_progcache cleans up unused entries eagerly when: 47 : 48 : 1. a database fork is cancelled (e.g. slot is rooted and competing 49 : history dies, or consensus layer prunes a fork) 50 : 2. a cache entry is orphaned (updated or invalidated by an epoch 51 : boundary) */ 52 : 53 : #include "fd_progcache.h" 54 : #include "fd_prog_load.h" 55 : #include "../accdb/fd_accdb_base.h" 56 : #include "../runtime/fd_runtime_const.h" 57 : 58 : #define FD_PROGCACHE_DEPTH_MAX (8192UL) 59 : 60 : struct fd_progcache_metrics { 61 : ulong lookup_cnt; 62 : ulong hit_cnt; 63 : ulong miss_cnt; 64 : ulong invalidate_cnt; 65 : ulong oom_heap_cnt; 66 : ulong oom_desc_cnt; 67 : ulong fill_cnt; 68 : ulong fill_tot_sz; 69 : ulong spill_cnt; 70 : ulong spill_tot_sz; 71 : ulong evict_cnt; 72 : ulong evict_tot_sz; 73 : ulong evict_freed_sz; 74 : ulong cum_pull_ticks; 75 : ulong cum_load_ticks; 76 : }; 77 : 78 : typedef struct fd_progcache_metrics fd_progcache_metrics_t; 79 : 80 : /* fd_progcache_t is a thread-local client to a program cache funk 81 : instance. This struct is quite large and therefore not local/stack 82 : declaration-friendly. */ 83 : 84 : struct fd_progcache { 85 : fd_progcache_join_t join[1]; 86 : fd_accdb_lineage_t lineage[1]; 87 : 88 : fd_progcache_metrics_t * metrics; 89 : 90 : uchar * scratch; 91 : ulong scratch_sz; 92 : 93 : uint spill_active; 94 : }; 95 : 96 : typedef struct fd_progcache fd_progcache_t; 97 : 98 : FD_PROTOTYPES_BEGIN 99 : 100 : extern FD_TL fd_progcache_metrics_t fd_progcache_metrics_default; 101 : 102 : /* Constructor */ 103 : 104 : static inline ulong 105 0 : fd_progcache_align( void ) { 106 0 : return alignof(fd_progcache_t); 107 0 : } 108 : 109 : static inline ulong 110 0 : fd_progcache_footprint( void ) { 111 0 : return sizeof(fd_progcache_t); 112 0 : } 113 : 114 : static inline fd_progcache_t * 115 0 : fd_progcache_new( void * ljoin ) { 116 0 : return ljoin; 117 0 : } 118 : 119 : static inline void * 120 0 : fd_progcache_delete( void * ljoin ) { 121 0 : return ljoin; 122 0 : } 123 : 124 : /* fd_progcache_join joins the caller to a program cache shmem instance. 125 : scratch points to a FD_PROGCACHE_SCRATCH_ALIGN aligned scratch buffer 126 : and scratch_sz is the size of the largest program/ELF binary that is 127 : going to be loaded (typically max account data sz). */ 128 : 129 : fd_progcache_t * 130 : fd_progcache_join( fd_progcache_t * ljoin, 131 : fd_progcache_shmem_t * shmem, 132 : uchar * scratch, 133 : ulong scratch_sz ); 134 : 135 12 : #define FD_PROGCACHE_SCRATCH_ALIGN (64UL) 136 12 : #define FD_PROGCACHE_SCRATCH_FOOTPRINT FD_RUNTIME_ACC_SZ_MAX 137 : 138 : /* fd_progcache_leave detaches the caller from a program cache. */ 139 : 140 : void * 141 : fd_progcache_leave( fd_progcache_t * cache, 142 : fd_progcache_shmem_t ** opt_shmem ); 143 : 144 : /* Record-level operations ********************************************/ 145 : 146 : /* fd_progcache_peek queries the program cache for an existing cache 147 : entry. Does not fill the cache. Returns a pointer to the entry on 148 : cache hit. Returns NULL on cache miss. It is the caller's 149 : responsibility to release the returned record with 150 : fd_progcache_rec_close. */ 151 : 152 : fd_progcache_rec_t * /* read locked */ 153 : fd_progcache_peek( fd_progcache_t * cache, 154 : fd_funk_txn_xid_t const * xid, 155 : void const * prog_addr, 156 : ulong epoch_slot0 ); 157 : 158 : /* fd_progcache_pull loads a program from cache, filling the cache if 159 : necessary. The load operation can have a number of outcomes: 160 : - Returns a pointer to an existing cache entry (cache hit, state 161 : either "Loaded" or "FailedVerification") 162 : - Returns a pointer to a newly created cache entry (cache fill, 163 : state either "Loaded" or "FailedVerification") 164 : - Returns NULL if the requested program account is not deployed (i.e. 165 : account is missing, the program is under visibility delay, or user 166 : has not finished uploading the program) 167 : In other words, this method guarantees to return a cache entry if a 168 : deployed program was found in the account database, and the program 169 : either loaded successfully, or failed ELF/bytecode verification. 170 : It is the caller's responsibility to release the returned record with 171 : fd_progcache_rec_close. */ 172 : 173 : fd_progcache_rec_t * /* read locked */ 174 : fd_progcache_pull( fd_progcache_t * cache, 175 : fd_accdb_user_t * accdb, 176 : fd_funk_txn_xid_t const * xid, 177 : void const * prog_addr, 178 : fd_prog_load_env_t const * env ); 179 : 180 : /* fd_progcache_invalidate marks the program at the given address as 181 : invalidated (typically due to a change of program content). This 182 : creates a non-executable cache entry at the given xid. 183 : 184 : After a program has been invalidated at xid, it is forbidden to pull 185 : the same entry at the same xid. (Invalidations should happen after 186 : replaying transactions). 187 : 188 : Assumes that xid is a valid fork graph node (not rooted) until 189 : invalidate returns. */ 190 : 191 : void 192 : fd_progcache_invalidate( fd_progcache_t * cache, 193 : fd_funk_txn_xid_t const * xid, 194 : void const * prog_addr, 195 : ulong slot ); 196 : 197 : /* fd_progcache_rec_close releases a cache record handle returned by 198 : fd_progcache_{pull,peek}. */ 199 : 200 : void 201 : fd_progcache_rec_close( fd_progcache_t * cache, 202 : fd_progcache_rec_t * rec ); 203 : 204 : FD_PROTOTYPES_END 205 : 206 : #endif /* HEADER_fd_src_flamenco_fd_progcache_h */