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