Line data Source code
1 : #include "fd_adminctl.h"
2 :
3 : #include "../../util/log/fd_log.h"
4 :
5 : #include <errno.h>
6 : #include <signal.h>
7 : #include <unistd.h>
8 :
9 0 : #define FD_ADMINCTL_MAGIC (0xF17EDA2C37AD0202UL)
10 0 : #define FD_ADMINCTL_STATE_MASK (7UL)
11 0 : #define FD_ADMINCTL_SEQ_SHIFT (3UL)
12 0 : #define FD_ADMINCTL_SEQ_BITS (8UL)
13 0 : #define FD_ADMINCTL_SEQ_VALUE_MASK ((1UL<<FD_ADMINCTL_SEQ_BITS)-1UL)
14 0 : #define FD_ADMINCTL_TS_SHIFT (FD_ADMINCTL_SEQ_SHIFT+FD_ADMINCTL_SEQ_BITS)
15 0 : #define FD_ADMINCTL_TS_BITS (FD_ADMINCTL_PID_SHIFT-FD_ADMINCTL_TS_SHIFT)
16 0 : #define FD_ADMINCTL_TS_VALUE_MASK ((1UL<<FD_ADMINCTL_TS_BITS)-1UL)
17 0 : #define FD_ADMINCTL_PID_SHIFT (32UL)
18 : #define FD_ADMINCTL_RESERVE_TTL (5UL)
19 :
20 0 : #define FD_ADMINCTL_STATE_FREE (0UL)
21 0 : #define FD_ADMINCTL_STATE_RESERVED (1UL)
22 : #define FD_ADMINCTL_STATE_PUBLISHED (2UL)
23 0 : #define FD_ADMINCTL_STATE_PROCESSING (3UL)
24 : #define FD_ADMINCTL_STATE_DONE (4UL)
25 :
26 : struct fd_adminctl_slot {
27 : ulong state_pid_seq;
28 : ulong cmd;
29 : ulong result;
30 : ulong payload_sz;
31 : uchar payload[ FD_ADMINCTL_PAYLOAD_MAX ];
32 : ulong step_cnt; /* step lines added to the command, the newest FD_ADMINCTL_STEP_CNT are in step */
33 : char step[ FD_ADMINCTL_STEP_CNT ][ FD_ADMINCTL_STEP_MAX ];
34 : };
35 : typedef struct fd_adminctl_slot fd_adminctl_slot_t;
36 :
37 : struct fd_adminctl_private {
38 : ulong magic; /* ==FD_ADMINCTL_MAGIC */
39 : ulong next_request_id;
40 : ulong poll_idx;
41 : fd_adminctl_slot_t slots[ FD_ADMINCTL_SLOT_CNT ];
42 : };
43 : typedef struct fd_adminctl_private fd_adminctl_t;
44 :
45 : static inline ulong
46 : fd_adminctl_state_pid_seq( uint pid,
47 : ulong seq,
48 : ulong reserve_ts_sec,
49 0 : ulong state ) {
50 0 : return (((ulong)pid)<<FD_ADMINCTL_PID_SHIFT) |
51 0 : ((reserve_ts_sec & FD_ADMINCTL_TS_VALUE_MASK)<<FD_ADMINCTL_TS_SHIFT) |
52 0 : ((seq & FD_ADMINCTL_SEQ_VALUE_MASK)<<FD_ADMINCTL_SEQ_SHIFT) |
53 0 : state;
54 0 : }
55 :
56 : static inline ulong
57 0 : fd_adminctl_state( ulong state_pid_seq ) {
58 0 : return state_pid_seq & FD_ADMINCTL_STATE_MASK;
59 0 : }
60 :
61 : static inline ulong
62 : fd_adminctl_state_update( ulong state_pid_seq,
63 0 : ulong state ) {
64 0 : return (state_pid_seq & ~FD_ADMINCTL_STATE_MASK) | state;
65 0 : }
66 :
67 : static inline ulong
68 0 : fd_adminctl_reserve_ts_sec( ulong state_pid_seq ) {
69 0 : return (state_pid_seq>>FD_ADMINCTL_TS_SHIFT) & FD_ADMINCTL_TS_VALUE_MASK;
70 0 : }
71 :
72 : static inline uint
73 0 : fd_adminctl_pid( ulong state_pid_seq ) {
74 0 : return (uint)(state_pid_seq>>FD_ADMINCTL_PID_SHIFT);
75 0 : }
76 :
77 : static inline ulong
78 0 : fd_adminctl_now_sec( void ) {
79 0 : return (ulong)(fd_log_wallclock() / 1000000000L);
80 0 : }
81 :
82 : static inline ulong
83 : fd_adminctl_elapsed_sec( ulong now_sec,
84 0 : ulong then_sec ) {
85 0 : return (now_sec - then_sec) & FD_ADMINCTL_TS_VALUE_MASK;
86 0 : }
87 :
88 : static int
89 0 : fd_adminctl_pid_alive( uint pid ) {
90 0 : if( FD_UNLIKELY( !pid ) ) return 0;
91 0 : if( FD_UNLIKELY( kill( (pid_t)pid, 0 ) ) ) {
92 0 : if( FD_LIKELY( errno==ESRCH ) ) return 0;
93 0 : return 1;
94 0 : }
95 0 : return 1;
96 0 : }
97 :
98 : static fd_adminctl_slot_t *
99 : fd_adminctl_slot_laddr( fd_adminctl_t * adminctl,
100 0 : ulong slot_id ) {
101 0 : return &adminctl->slots[ slot_id ];
102 0 : }
103 :
104 : FD_FN_CONST ulong
105 0 : fd_adminctl_align( void ) {
106 0 : return FD_ADMINCTL_ALIGN;
107 0 : }
108 :
109 : FD_FN_CONST ulong
110 0 : fd_adminctl_footprint( void ) {
111 0 : return sizeof(fd_adminctl_t);
112 0 : }
113 :
114 : void *
115 0 : fd_adminctl_new( void * shmem ) {
116 0 : fd_adminctl_t * adminctl = (fd_adminctl_t *)shmem;
117 :
118 0 : if( FD_UNLIKELY( !shmem ) ) {
119 0 : FD_LOG_WARNING(( "NULL shmem" ));
120 0 : return NULL;
121 0 : }
122 :
123 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shmem, fd_adminctl_align() ) ) ) {
124 0 : FD_LOG_WARNING(( "misaligned shmem" ));
125 0 : return NULL;
126 0 : }
127 :
128 0 : fd_memset( adminctl, 0, fd_adminctl_footprint() );
129 :
130 0 : FD_COMPILER_MFENCE();
131 0 : FD_VOLATILE( adminctl->magic ) = FD_ADMINCTL_MAGIC;
132 :
133 0 : return (void *)adminctl;
134 0 : }
135 :
136 : fd_adminctl_t *
137 0 : fd_adminctl_join( void * shadminctl ) {
138 :
139 0 : if( FD_UNLIKELY( !shadminctl ) ) {
140 0 : FD_LOG_WARNING(( "NULL shadminctl" ));
141 0 : return NULL;
142 0 : }
143 :
144 0 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)shadminctl, fd_adminctl_align() ) ) ) {
145 0 : FD_LOG_WARNING(( "misaligned shadminctl" ));
146 0 : return NULL;
147 0 : }
148 :
149 0 : fd_adminctl_t * adminctl = (fd_adminctl_t *)shadminctl;
150 :
151 0 : if( FD_UNLIKELY( adminctl->magic!=FD_ADMINCTL_MAGIC ) ) {
152 0 : FD_LOG_WARNING(( "bad magic" ));
153 0 : return NULL;
154 0 : }
155 :
156 0 : return adminctl;
157 0 : }
158 :
159 : ulong
160 : fd_adminctl_reserve( fd_adminctl_t * adminctl,
161 : void ** payload_out,
162 0 : ulong * payload_max_out ) {
163 :
164 : /* Identify the command process with a pid and a monotonically
165 : increasing sequence number. */
166 0 : uint pid = (uint)getpid();
167 0 : ulong seq = FD_ATOMIC_ADD_AND_FETCH( &adminctl->next_request_id, 1UL );
168 :
169 : /* Track the pid of a process which may be hung */
170 0 : uint hung_pid = UINT_MAX;
171 0 : ulong now_sec = fd_adminctl_now_sec();
172 :
173 0 : for( ulong slot_id=0UL; slot_id<FD_ADMINCTL_SLOT_CNT; slot_id++ ) {
174 0 : fd_adminctl_slot_t * slot = fd_adminctl_slot_laddr( adminctl, slot_id );
175 0 : ulong state_pid_seq = __atomic_load_n( &slot->state_pid_seq, __ATOMIC_ACQUIRE );
176 0 : uint owner_pid = fd_adminctl_pid( state_pid_seq );
177 :
178 : /* Reclaim a slot if it's a dead command. The conditions for
179 : reclaiming a slot are:
180 : - the slot is in a command owned state (RESERVED or DONE)
181 : - the process for the slot is dead */
182 0 : ulong state = fd_adminctl_state( state_pid_seq );
183 0 : if( FD_UNLIKELY( (state==FD_ADMINCTL_STATE_RESERVED || state==FD_ADMINCTL_STATE_DONE) && !fd_adminctl_pid_alive( owner_pid ) ) ) {
184 0 : FD_LOG_WARNING(( "a dead command process (pid=%u) has been detected. This is likely the "
185 0 : "result of a process being killed before it issued the command "
186 0 : "or consumed the result of the command", owner_pid ));
187 0 : ulong free_state_pid_seq = fd_adminctl_state_update( state_pid_seq, FD_ADMINCTL_STATE_FREE );
188 0 : if( FD_LIKELY( FD_ATOMIC_CAS( &slot->state_pid_seq, state_pid_seq, free_state_pid_seq )==state_pid_seq ) ) {
189 0 : state_pid_seq = __atomic_load_n( &slot->state_pid_seq, __ATOMIC_ACQUIRE );
190 0 : state = fd_adminctl_state( state_pid_seq );
191 0 : } else {
192 0 : continue;
193 0 : }
194 0 : }
195 :
196 : /* If a slot is reserved and seems to be hung, track the pid to
197 : notify the caller. Note that kill(pid,0) only checks the pid
198 : number; a reused pid can make a dead owner look hung. */
199 0 : if( FD_UNLIKELY( state==FD_ADMINCTL_STATE_RESERVED ) ) {
200 0 : ulong reserve_ts_sec = fd_adminctl_reserve_ts_sec( state_pid_seq );
201 0 : if( FD_UNLIKELY( fd_adminctl_pid_alive( owner_pid ) ) ) {
202 0 : if( FD_UNLIKELY( fd_adminctl_elapsed_sec( now_sec, reserve_ts_sec )>FD_ADMINCTL_RESERVE_TTL ) ) {
203 0 : hung_pid = owner_pid;
204 0 : }
205 0 : }
206 0 : }
207 :
208 0 : if( FD_UNLIKELY( state!=FD_ADMINCTL_STATE_FREE ) ) continue;
209 :
210 0 : ulong reserved_state_pid_seq = fd_adminctl_state_pid_seq( pid, seq, now_sec, FD_ADMINCTL_STATE_RESERVED );
211 0 : if( FD_UNLIKELY( FD_ATOMIC_CAS( &slot->state_pid_seq, state_pid_seq, reserved_state_pid_seq )!=state_pid_seq ) ) continue;
212 :
213 0 : fd_memzero_explicit( slot->payload, sizeof(slot->payload) );
214 0 : *payload_out = slot->payload;
215 0 : *payload_max_out = FD_ADMINCTL_PAYLOAD_MAX;
216 0 : return slot_id;
217 0 : }
218 :
219 0 : if( FD_UNLIKELY( hung_pid!=UINT_MAX ) ) {
220 0 : FD_LOG_WARNING(( "Unable to run command as there is another process (pid=%u) attempting to run a "
221 0 : "command which appears to be hung (or the pid may have been reused) and has not "
222 0 : "executed for over 5 seconds. Consider forcefully killing the process and "
223 0 : "retrying the command.", hung_pid ));
224 :
225 0 : }
226 :
227 0 : return ULONG_MAX;
228 0 : }
229 :
230 : void
231 : fd_adminctl_publish( fd_adminctl_t * adminctl,
232 : ulong slot_id,
233 : ulong cmd_id,
234 0 : ulong payload_sz ) {
235 :
236 : /* Now that the caller has written the payload into the adminctl's
237 : app region, we can publish the command to the admin tile. At this
238 : point, the admin tile will own the command and return a result. */
239 0 : if( FD_UNLIKELY( slot_id>=FD_ADMINCTL_SLOT_CNT ) ) FD_LOG_CRIT(( "bad slot_id %lu", slot_id ));
240 :
241 0 : fd_adminctl_slot_t * slot = fd_adminctl_slot_laddr( adminctl, slot_id );
242 0 : ulong state_pid_seq = __atomic_load_n( &slot->state_pid_seq, __ATOMIC_ACQUIRE );
243 0 : if( FD_UNLIKELY( fd_adminctl_state( state_pid_seq )!=FD_ADMINCTL_STATE_RESERVED ) ) FD_LOG_CRIT(( "adminctl publish without reservation" ));
244 :
245 0 : slot->cmd = cmd_id;
246 0 : slot->payload_sz = payload_sz;
247 0 : slot->step_cnt = 0UL;
248 :
249 0 : if( FD_UNLIKELY( FD_ATOMIC_CAS( &slot->state_pid_seq,
250 0 : state_pid_seq,
251 0 : fd_adminctl_state_update( state_pid_seq, FD_ADMINCTL_STATE_PUBLISHED ) )!=state_pid_seq ) ) {
252 0 : FD_LOG_ERR(( "The command process is in an unexpected and invalid state while sending the "
253 0 : "the command to the running firedancer. This is likely the result of a bug. "
254 0 : "Please report this to the firedancer team." ));
255 0 : }
256 0 : }
257 :
258 : ulong
259 : fd_adminctl_poll( fd_adminctl_t * adminctl,
260 : ulong * slot_id_out,
261 : void ** payload_out,
262 0 : ulong * payload_sz_out ) {
263 :
264 0 : ulong slot_id = adminctl->poll_idx++ % FD_ADMINCTL_SLOT_CNT;
265 0 : fd_adminctl_slot_t * slot = fd_adminctl_slot_laddr( adminctl, slot_id );
266 :
267 0 : ulong state_pid_seq = __atomic_load_n( &slot->state_pid_seq, __ATOMIC_ACQUIRE );
268 0 : FD_COMPILER_MFENCE();
269 :
270 0 : if( FD_UNLIKELY( fd_adminctl_state( state_pid_seq )!=FD_ADMINCTL_STATE_PUBLISHED ) ) return FD_ADMINCTL_CMD_IDLE;
271 :
272 0 : ulong cmd_id = slot->cmd;
273 0 : ulong payload_sz = slot->payload_sz;
274 :
275 0 : ulong processing_state_pid_seq = fd_adminctl_state_update( state_pid_seq, FD_ADMINCTL_STATE_PROCESSING );
276 0 : if( FD_UNLIKELY( FD_ATOMIC_CAS( &slot->state_pid_seq, state_pid_seq, processing_state_pid_seq )!=state_pid_seq ) ) return FD_ADMINCTL_CMD_IDLE;
277 :
278 0 : *slot_id_out = slot_id;
279 0 : *payload_out = slot->payload;
280 0 : *payload_sz_out = payload_sz;
281 0 : return cmd_id;
282 0 : }
283 :
284 : void
285 : fd_adminctl_complete_response( fd_adminctl_t * adminctl,
286 : ulong slot_id,
287 : ulong result,
288 : void const * resp,
289 0 : ulong resp_sz ) {
290 :
291 0 : if( FD_UNLIKELY( resp_sz>FD_ADMINCTL_PAYLOAD_MAX ) ) FD_LOG_CRIT(( "bad resp_sz %lu", resp_sz ));
292 :
293 0 : fd_adminctl_slot_t * slot = fd_adminctl_slot_laddr( adminctl, slot_id );
294 0 : ulong state_pid_seq = __atomic_load_n( &slot->state_pid_seq, __ATOMIC_ACQUIRE );
295 :
296 0 : if( FD_UNLIKELY( fd_adminctl_state( state_pid_seq )!=FD_ADMINCTL_STATE_PROCESSING ) ) FD_LOG_ERR(( "adminctl complete without processing command" ));
297 :
298 0 : fd_memzero_explicit( slot->payload, slot->payload_sz );
299 0 : if( FD_UNLIKELY( resp_sz ) ) memcpy( slot->payload, resp, resp_sz );
300 0 : slot->payload_sz = resp_sz;
301 0 : slot->result = result;
302 0 : if( FD_UNLIKELY( FD_ATOMIC_CAS( &slot->state_pid_seq,
303 0 : state_pid_seq,
304 0 : fd_adminctl_state_update( state_pid_seq, FD_ADMINCTL_STATE_DONE ) )!=state_pid_seq ) )
305 0 : FD_LOG_ERR(( "invariant violation: adminctl_complete state update failed" ));
306 0 : }
307 :
308 : void
309 : fd_adminctl_complete( fd_adminctl_t * adminctl,
310 : ulong slot_id,
311 0 : ulong result ) {
312 0 : fd_adminctl_complete_response( adminctl, slot_id, result, NULL, 0UL );
313 0 : }
314 :
315 : void
316 : fd_adminctl_step( fd_adminctl_t * adminctl,
317 : ulong slot_id,
318 0 : char const * step ) {
319 0 : fd_adminctl_slot_t * slot = fd_adminctl_slot_laddr( adminctl, slot_id );
320 0 : if( FD_UNLIKELY( fd_adminctl_state( __atomic_load_n( &slot->state_pid_seq, __ATOMIC_ACQUIRE ) )!=FD_ADMINCTL_STATE_PROCESSING ) ) {
321 0 : FD_LOG_ERR(( "adminctl step without processing command" ));
322 0 : }
323 0 : ulong cnt = slot->step_cnt;
324 0 : fd_cstr_ncpy( slot->step[ cnt%FD_ADMINCTL_STEP_CNT ], step, FD_ADMINCTL_STEP_MAX );
325 0 : FD_COMPILER_MFENCE();
326 0 : FD_VOLATILE( slot->step_cnt ) = cnt+1UL;
327 0 : }
328 :
329 : static void
330 : fd_adminctl_show_steps( fd_adminctl_slot_t const * slot,
331 : ulong * shown,
332 0 : fd_adminctl_step_fn_t step_fn ) {
333 0 : ulong cnt = FD_VOLATILE_CONST( slot->step_cnt );
334 0 : FD_COMPILER_MFENCE();
335 0 : if( FD_UNLIKELY( cnt-*shown>FD_ADMINCTL_STEP_CNT-1UL ) ) *shown = cnt-(FD_ADMINCTL_STEP_CNT-1UL);
336 0 : for( ; *shown<cnt; (*shown)++ ) {
337 0 : char line[ FD_ADMINCTL_STEP_MAX ];
338 0 : fd_memcpy( line, slot->step[ *shown%FD_ADMINCTL_STEP_CNT ], FD_ADMINCTL_STEP_MAX );
339 0 : line[ FD_ADMINCTL_STEP_MAX-1UL ] = '\0';
340 0 : FD_COMPILER_MFENCE();
341 0 : if( FD_UNLIKELY( FD_VOLATILE_CONST( slot->step_cnt )-*shown>FD_ADMINCTL_STEP_CNT-1UL ) ) continue; /* overwritten while copied */
342 0 : step_fn( line );
343 0 : }
344 0 : }
345 :
346 : ulong
347 : fd_adminctl_wait_response( fd_adminctl_t * adminctl,
348 : ulong slot_id,
349 : void * resp,
350 : ulong resp_max,
351 0 : ulong * resp_sz_out ) {
352 0 : return fd_adminctl_wait_steps( adminctl, slot_id, resp, resp_max, resp_sz_out, NULL );
353 0 : }
354 :
355 : ulong
356 : fd_adminctl_wait_steps( fd_adminctl_t * adminctl,
357 : ulong slot_id,
358 : void * resp,
359 : ulong resp_max,
360 : ulong * resp_sz_out,
361 0 : fd_adminctl_step_fn_t step_fn ) {
362 :
363 0 : fd_adminctl_slot_t * slot = fd_adminctl_slot_laddr( adminctl, slot_id );
364 :
365 0 : uint pid = (uint)getpid();
366 0 : ulong shown = 0UL;
367 :
368 0 : for(;;) {
369 0 : ulong state_pid_seq = __atomic_load_n( &slot->state_pid_seq, __ATOMIC_ACQUIRE );
370 0 : FD_COMPILER_MFENCE();
371 :
372 0 : ulong state = fd_adminctl_state( state_pid_seq );
373 0 : if( FD_UNLIKELY( fd_adminctl_pid( state_pid_seq )!=pid ||
374 0 : (state!=FD_ADMINCTL_STATE_PUBLISHED &&
375 0 : state!=FD_ADMINCTL_STATE_PROCESSING &&
376 0 : state!=FD_ADMINCTL_STATE_DONE) ) ) {
377 0 : FD_LOG_ERR(( "The command process is in an unexpected and invalid state while waiting for "
378 0 : "the command to complete. This is likely the result of a bug in firedancer. "
379 0 : "Please report this to the firedancer team." ));
380 0 : }
381 :
382 0 : if( FD_UNLIKELY( step_fn ) ) fd_adminctl_show_steps( slot, &shown, step_fn );
383 :
384 0 : if( FD_LIKELY( state==FD_ADMINCTL_STATE_DONE ) ) {
385 0 : ulong result = slot->result;
386 0 : ulong resp_sz = fd_ulong_min( slot->payload_sz, FD_ADMINCTL_PAYLOAD_MAX );
387 0 : if( FD_UNLIKELY( resp ) ) memcpy( resp, slot->payload, fd_ulong_min( resp_sz, resp_max ) );
388 0 : if( FD_UNLIKELY( resp_sz_out ) ) *resp_sz_out = resp_sz;
389 0 : fd_memzero_explicit( slot->payload, resp_sz );
390 0 : if( FD_LIKELY( FD_ATOMIC_CAS( &slot->state_pid_seq,
391 0 : state_pid_seq,
392 0 : fd_adminctl_state_update( state_pid_seq, FD_ADMINCTL_STATE_FREE ) )==state_pid_seq ) ) {
393 0 : return result;
394 0 : }
395 0 : }
396 0 : FD_SPIN_PAUSE();
397 0 : }
398 0 : }
399 :
400 : ulong
401 : fd_adminctl_wait( fd_adminctl_t * adminctl,
402 0 : ulong slot_id ) {
403 0 : return fd_adminctl_wait_response( adminctl, slot_id, NULL, 0UL, NULL );
404 0 : }
|