vcl: move forking logic to vls
[vpp.git] / src / vcl / vcl_locked.c
1 /*
2  * Copyright (c) 2019 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <vcl/vcl_locked.h>
17 #include <vcl/vcl_private.h>
18
19 typedef struct vcl_locked_session_
20 {
21   clib_spinlock_t lock;
22   u32 session_index;
23   u32 worker_index;
24   u32 vls_index;
25   u32 flags;
26   u32 *workers_subscribed;
27 } vcl_locked_session_t;
28
29 typedef struct vcl_main_
30 {
31   vcl_locked_session_t *vls_pool;
32   clib_rwlock_t vls_table_lock;
33   uword *session_index_to_vlsh_table;
34 } vls_main_t;
35
36 vls_main_t vls_main;
37 vls_main_t *vlsm = &vls_main;
38
39 static inline void
40 vls_table_rlock (void)
41 {
42   clib_rwlock_reader_lock (&vlsm->vls_table_lock);
43 }
44
45 static inline void
46 vls_table_runlock (void)
47 {
48   clib_rwlock_reader_unlock (&vlsm->vls_table_lock);
49 }
50
51 static inline void
52 vls_table_wlock (void)
53 {
54   clib_rwlock_writer_lock (&vlsm->vls_table_lock);
55 }
56
57 static inline void
58 vls_table_wunlock (void)
59 {
60   clib_rwlock_writer_unlock (&vlsm->vls_table_lock);
61 }
62
63 static inline vcl_session_handle_t
64 vls_to_sh (vcl_locked_session_t * vls)
65 {
66   return vcl_session_handle_from_index (vls->session_index);
67 }
68
69 static inline vcl_session_handle_t
70 vls_to_sh_tu (vcl_locked_session_t * vls)
71 {
72   vcl_session_handle_t sh;
73   sh = vls_to_sh (vls);
74   vls_table_runlock ();
75   return sh;
76 }
77
78 static vls_handle_t
79 vls_alloc (vcl_session_handle_t sh)
80 {
81   vcl_locked_session_t *vls;
82
83   vls_table_wlock ();
84   pool_get (vlsm->vls_pool, vls);
85   vls->session_index = vppcom_session_index (sh);
86   vls->worker_index = vppcom_session_worker (sh);
87   vls->vls_index = vls - vlsm->vls_pool;
88   hash_set (vlsm->session_index_to_vlsh_table, vls->session_index,
89             vls->vls_index);
90   clib_spinlock_init (&vls->lock);
91   vls_table_wunlock ();
92   return vls->vls_index;
93 }
94
95 static vcl_locked_session_t *
96 vls_get (vls_handle_t vlsh)
97 {
98   if (pool_is_free_index (vlsm->vls_pool, vlsh))
99     return 0;
100   return pool_elt_at_index (vlsm->vls_pool, vlsh);
101 }
102
103 static void
104 vls_free (vcl_locked_session_t * fde)
105 {
106   ASSERT (fde != 0);
107   hash_unset (vlsm->session_index_to_vlsh_table, fde->session_index);
108   clib_spinlock_free (&fde->lock);
109   pool_put (vlsm->vls_pool, fde);
110 }
111
112 static vcl_locked_session_t *
113 vls_get_and_lock (vls_handle_t vlsh)
114 {
115   vcl_locked_session_t *vls;
116   if (pool_is_free_index (vlsm->vls_pool, vlsh))
117     return 0;
118   vls = pool_elt_at_index (vlsm->vls_pool, vlsh);
119   clib_spinlock_lock (&vls->lock);
120   return vls;
121 }
122
123 static vcl_locked_session_t *
124 vls_get_w_dlock (vls_handle_t vlsh)
125 {
126   vcl_locked_session_t *vls;
127   vls_table_rlock ();
128   vls = vls_get_and_lock (vlsh);
129   if (!vls)
130     vls_table_runlock ();
131   return vls;
132 }
133
134 static inline void
135 vls_unlock (vcl_locked_session_t * vls)
136 {
137   clib_spinlock_unlock (&vls->lock);
138 }
139
140 static inline void
141 vls_get_and_unlock (vls_handle_t vlsh)
142 {
143   vcl_locked_session_t *vls;
144   vls_table_rlock ();
145   vls = vls_get (vlsh);
146   vls_unlock (vls);
147   vls_table_runlock ();
148 }
149
150 static inline void
151 vls_dunlock (vcl_locked_session_t * vls)
152 {
153   vls_unlock (vls);
154   vls_table_runlock ();
155 }
156
157 static void
158 vls_get_and_free (vls_handle_t vlsh)
159 {
160   vcl_locked_session_t *vls;
161
162   vls_table_wlock ();
163   vls = vls_get (vlsh);
164   vls_free (vls);
165   vls_table_wunlock ();
166 }
167
168 u8
169 vls_is_shared (vcl_locked_session_t * vls)
170 {
171   return vec_len (vls->workers_subscribed);
172 }
173
174 int
175 vls_unshare_session (vcl_locked_session_t * vls)
176 {
177   vcl_worker_t *wrk = vcl_worker_get_current ();
178   vcl_session_t *s;
179   int i;
180
181   for (i = 0; i < vec_len (vls->workers_subscribed); i++)
182     {
183       if (vls->workers_subscribed[i] != wrk->wrk_index)
184         continue;
185
186       s = vcl_session_get (wrk, vls->session_index);
187       if (s->rx_fifo)
188         {
189           svm_fifo_del_subscriber (s->rx_fifo, wrk->vpp_wrk_index);
190           svm_fifo_del_subscriber (s->tx_fifo, wrk->vpp_wrk_index);
191         }
192       vec_del1 (vls->workers_subscribed, i);
193       vcl_session_cleanup (wrk, s, vcl_session_handle (s),
194                            0 /* do_disconnect */ );
195       return 0;
196     }
197
198   /* Assumption is that unshare is only called if session is shared.
199    * So shared_workers must be non-empty if the worker is the owner */
200   if (vls->worker_index == wrk->wrk_index)
201     {
202       s = vcl_session_get (wrk, vls->session_index);
203       vls->worker_index = vls->workers_subscribed[0];
204       vec_del1 (vls->workers_subscribed, 0);
205       vcl_send_session_worker_update (wrk, s, vls->worker_index);
206       if (vec_len (vls->workers_subscribed))
207         clib_warning ("more workers need to be updated");
208     }
209
210   return 0;
211 }
212
213 void
214 vls_share_vcl_session (vcl_worker_t * wrk, vcl_session_t * s)
215 {
216   vcl_locked_session_t *vls;
217
218   vls = vls_get_w_dlock (vls_session_index_to_vlsh (s->session_index));
219   if (!vls)
220     return;
221   vec_add1 (vls->workers_subscribed, wrk->wrk_index);
222   if (s->rx_fifo)
223     {
224       svm_fifo_add_subscriber (s->rx_fifo, wrk->vpp_wrk_index);
225       svm_fifo_add_subscriber (s->tx_fifo, wrk->vpp_wrk_index);
226     }
227   vls_dunlock (vls);
228 }
229
230 void
231 vls_worker_copy_on_fork (vcl_worker_t * parent_wrk)
232 {
233   vcl_worker_t *wrk = vcl_worker_get_current ();
234   vcl_session_t *s;
235
236   wrk->vpp_event_queues = vec_dup (parent_wrk->vpp_event_queues);
237   wrk->sessions = pool_dup (parent_wrk->sessions);
238   wrk->session_index_by_vpp_handles =
239     hash_dup (parent_wrk->session_index_by_vpp_handles);
240
241   /* *INDENT-OFF* */
242   pool_foreach (s, wrk->sessions, ({
243     vls_share_vcl_session (wrk, s);
244   }));
245   /* *INDENT-ON* */
246 }
247
248 int
249 vls_write (vls_handle_t vlsh, void *buf, size_t nbytes)
250 {
251   vcl_locked_session_t *vls;
252   int rv;
253
254   if (!(vls = vls_get_w_dlock (vlsh)))
255     return VPPCOM_EBADFD;
256   rv = vppcom_session_write (vls_to_sh_tu (vls), buf, nbytes);
257   vls_get_and_unlock (vlsh);
258   return rv;
259 }
260
261 int
262 vls_write_msg (vls_handle_t vlsh, void *buf, size_t nbytes)
263 {
264   vcl_locked_session_t *vls;
265   int rv;
266
267   if (!(vls = vls_get_w_dlock (vlsh)))
268     return VPPCOM_EBADFD;
269   rv = vppcom_session_write_msg (vls_to_sh_tu (vls), buf, nbytes);
270   vls_get_and_unlock (vlsh);
271   return rv;
272 }
273
274 int
275 vls_sendto (vls_handle_t vlsh, void *buf, int buflen, int flags,
276             vppcom_endpt_t * ep)
277 {
278   vcl_locked_session_t *vls;
279   int rv;
280
281   if (!(vls = vls_get_w_dlock (vlsh)))
282     return VPPCOM_EBADFD;
283   rv = vppcom_session_sendto (vls_to_sh_tu (vls), buf, buflen, flags, ep);
284   vls_get_and_unlock (vlsh);
285   return rv;
286 }
287
288 ssize_t
289 vls_read (vls_handle_t vlsh, void *buf, size_t nbytes)
290 {
291   vcl_locked_session_t *vls;
292   int rv;
293
294   if (!(vls = vls_get_w_dlock (vlsh)))
295     return VPPCOM_EBADFD;
296   rv = vppcom_session_read (vls_to_sh_tu (vls), buf, nbytes);
297   vls_get_and_unlock (vlsh);
298   return rv;
299 }
300
301 ssize_t
302 vls_recvfrom (vls_handle_t vlsh, void *buffer, uint32_t buflen, int flags,
303               vppcom_endpt_t * ep)
304 {
305   vcl_locked_session_t *vls;
306   int rv;
307
308   if (!(vls = vls_get_w_dlock (vlsh)))
309     return VPPCOM_EBADFD;
310   rv = vppcom_session_recvfrom (vls_to_sh_tu (vls), buffer, buflen, flags,
311                                 ep);
312   vls_get_and_unlock (vlsh);
313   return rv;
314 }
315
316 int
317 vls_attr (vls_handle_t vlsh, uint32_t op, void *buffer, uint32_t * buflen)
318 {
319   vcl_locked_session_t *vls;
320   int rv;
321
322   if (!(vls = vls_get_w_dlock (vlsh)))
323     return VPPCOM_EBADFD;
324   rv = vppcom_session_attr (vls_to_sh_tu (vls), op, buffer, buflen);
325   vls_get_and_unlock (vlsh);
326   return rv;
327 }
328
329 int
330 vls_bind (vls_handle_t vlsh, vppcom_endpt_t * ep)
331 {
332   vcl_locked_session_t *vls;
333   int rv;
334
335   if (!(vls = vls_get_w_dlock (vlsh)))
336     return VPPCOM_EBADFD;
337   rv = vppcom_session_bind (vls_to_sh_tu (vls), ep);
338   vls_get_and_unlock (vlsh);
339   return rv;
340 }
341
342 int
343 vls_listen (vls_handle_t vlsh, int q_len)
344 {
345   vcl_locked_session_t *vls;
346   int rv;
347
348   if (!(vls = vls_get_w_dlock (vlsh)))
349     return VPPCOM_EBADFD;
350   rv = vppcom_session_listen (vls_to_sh_tu (vls), q_len);
351   vls_get_and_unlock (vlsh);
352   return rv;
353 }
354
355 int
356 vls_connect (vls_handle_t vlsh, vppcom_endpt_t * server_ep)
357 {
358   vcl_locked_session_t *vls;
359   int rv;
360
361   if (!(vls = vls_get_w_dlock (vlsh)))
362     return VPPCOM_EBADFD;
363   rv = vppcom_session_connect (vls_to_sh_tu (vls), server_ep);
364   vls_get_and_unlock (vlsh);
365   return rv;
366 }
367
368 vls_handle_t
369 vls_accept (vls_handle_t listener_vlsh, vppcom_endpt_t * ep, int flags)
370 {
371   vls_handle_t accepted_vlsh;
372   vcl_locked_session_t *vls;
373   int sh;
374
375   if (!(vls = vls_get_w_dlock (listener_vlsh)))
376     return VPPCOM_EBADFD;
377   sh = vppcom_session_accept (vls_to_sh_tu (vls), ep, flags);
378   vls_get_and_unlock (listener_vlsh);
379   if (sh < 0)
380     return sh;
381   accepted_vlsh = vls_alloc (sh);
382   if (PREDICT_FALSE (accepted_vlsh == VLS_INVALID_HANDLE))
383     vppcom_session_close (sh);
384   return accepted_vlsh;
385 }
386
387 vls_handle_t
388 vls_create (uint8_t proto, uint8_t is_nonblocking)
389 {
390   vcl_session_handle_t sh;
391   vls_handle_t vlsh;
392
393   sh = vppcom_session_create (proto, is_nonblocking);
394   if (sh == INVALID_SESSION_ID)
395     return VLS_INVALID_HANDLE;
396
397   vlsh = vls_alloc (sh);
398   if (PREDICT_FALSE (vlsh == VLS_INVALID_HANDLE))
399     vppcom_session_close (sh);
400
401   return vlsh;
402 }
403
404 int
405 vls_close (vls_handle_t vlsh)
406 {
407   vcl_locked_session_t *vls;
408   vcl_session_handle_t sh;
409   int rv;
410
411   if (!(vls = vls_get_w_dlock (vlsh)))
412     return VPPCOM_EBADFD;
413
414   if (vls_is_shared (vls))
415     {
416       vls_unshare_session (vls);
417       vls_dunlock (vls);
418       return VPPCOM_OK;
419     }
420
421   sh = vls_to_sh (vls);
422   if ((rv = vppcom_session_close (sh)))
423     {
424       vls_dunlock (vls);
425       return rv;
426     }
427
428   vls_dunlock (vls);
429   vls_get_and_free (vlsh);
430   return rv;
431 }
432
433 vls_handle_t
434 vls_epoll_create (void)
435 {
436   vcl_session_handle_t sh;
437   vls_handle_t vlsh;
438
439   sh = vppcom_epoll_create ();
440   if (sh == INVALID_SESSION_ID)
441     return VLS_INVALID_HANDLE;
442
443   vlsh = vls_alloc (sh);
444   if (vlsh == VLS_INVALID_HANDLE)
445     vppcom_session_close (sh);
446
447   return vlsh;
448 }
449
450 int
451 vls_epoll_ctl (vls_handle_t ep_vlsh, int op, vls_handle_t vlsh,
452                struct epoll_event *event)
453 {
454   vcl_locked_session_t *ep_vls, *vls;
455   vcl_session_handle_t ep_sh, sh;
456   int rv;
457
458   vls_table_rlock ();
459   ep_vls = vls_get_and_lock (ep_vlsh);
460   vls = vls_get_and_lock (vlsh);
461   ep_sh = vls_to_sh (ep_vls);
462   sh = vls_to_sh (vls);
463   vls_table_runlock ();
464
465   rv = vppcom_epoll_ctl (ep_sh, op, sh, event);
466
467   vls_table_rlock ();
468   ep_vls = vls_get (ep_vlsh);
469   vls = vls_get (vlsh);
470   vls_unlock (vls);
471   vls_unlock (ep_vls);
472   vls_table_runlock ();
473   return rv;
474 }
475
476 int
477 vls_epoll_wait (vls_handle_t ep_vlsh, struct epoll_event *events,
478                 int maxevents, double wait_for_time)
479 {
480   vcl_locked_session_t *vls;
481   int rv;
482
483   if (!(vls = vls_get_w_dlock (ep_vlsh)))
484     return VPPCOM_EBADFD;
485   rv = vppcom_epoll_wait (vls_to_sh_tu (vls), events, maxevents,
486                           wait_for_time);
487   vls_get_and_unlock (ep_vlsh);
488   return rv;
489 }
490
491 vcl_session_handle_t
492 vlsh_to_sh (vls_handle_t vlsh)
493 {
494   vcl_locked_session_t *vls;
495   int rv;
496
497   vls = vls_get_w_dlock (vlsh);
498   if (!vls)
499     return INVALID_SESSION_ID;
500   rv = vls_to_sh (vls);
501   vls_dunlock (vls);
502   return rv;
503 }
504
505 vcl_session_handle_t
506 vlsh_to_session_index (vls_handle_t vlsh)
507 {
508   vcl_session_handle_t sh;
509   sh = vlsh_to_sh (vlsh);
510   return vppcom_session_index (sh);
511 }
512
513 vls_handle_t
514 vls_session_index_to_vlsh (uint32_t session_index)
515 {
516   vls_handle_t vlsh;
517   uword *vlshp;
518
519   vls_table_rlock ();
520   vlshp = hash_get (vlsm->session_index_to_vlsh_table, session_index);
521   vlsh = vlshp ? *vlshp : VLS_INVALID_HANDLE;
522   vls_table_runlock ();
523
524   return vlsh;
525 }
526
527 static void
528 vls_cleanup_forked_child (vcl_worker_t * wrk, vcl_worker_t * child_wrk)
529 {
530   vcl_worker_t *sub_child;
531   int tries = 0;
532
533   if (child_wrk->forked_child != ~0)
534     {
535       sub_child = vcl_worker_get_if_valid (child_wrk->forked_child);
536       if (sub_child)
537         {
538           /* Wait a bit, maybe the process is going away */
539           while (kill (sub_child->current_pid, 0) >= 0 && tries++ < 50)
540             usleep (1e3);
541           if (kill (sub_child->current_pid, 0) < 0)
542             vls_cleanup_forked_child (child_wrk, sub_child);
543         }
544     }
545   vcl_worker_cleanup (child_wrk, 1 /* notify vpp */ );
546   VDBG (0, "Cleaned up wrk %u", child_wrk->wrk_index);
547   wrk->forked_child = ~0;
548 }
549
550 static struct sigaction old_sa;
551
552 static void
553 vls_intercept_sigchld_handler (int signum, siginfo_t * si, void *uc)
554 {
555   vcl_worker_t *wrk, *child_wrk;
556
557   if (vcl_get_worker_index () == ~0)
558     return;
559
560   if (sigaction (SIGCHLD, &old_sa, 0))
561     {
562       VERR ("couldn't restore sigchld");
563       exit (-1);
564     }
565
566   wrk = vcl_worker_get_current ();
567   if (wrk->forked_child == ~0)
568     return;
569
570   child_wrk = vcl_worker_get_if_valid (wrk->forked_child);
571   if (!child_wrk)
572     goto done;
573
574   if (si && si->si_pid != child_wrk->current_pid)
575     {
576       VDBG (0, "unexpected child pid %u", si->si_pid);
577       goto done;
578     }
579   vls_cleanup_forked_child (wrk, child_wrk);
580
581 done:
582   if (old_sa.sa_flags & SA_SIGINFO)
583     {
584       void (*fn) (int, siginfo_t *, void *) = old_sa.sa_sigaction;
585       fn (signum, si, uc);
586     }
587   else
588     {
589       void (*fn) (int) = old_sa.sa_handler;
590       if (fn)
591         fn (signum);
592     }
593 }
594
595 static void
596 vls_incercept_sigchld ()
597 {
598   struct sigaction sa;
599   clib_memset (&sa, 0, sizeof (sa));
600   sa.sa_sigaction = vls_intercept_sigchld_handler;
601   sa.sa_flags = SA_SIGINFO;
602   if (sigaction (SIGCHLD, &sa, &old_sa))
603     {
604       VERR ("couldn't intercept sigchld");
605       exit (-1);
606     }
607 }
608
609 static void
610 vls_app_pre_fork (void)
611 {
612   vls_incercept_sigchld ();
613   vcl_flush_mq_events ();
614 }
615
616 static void
617 vls_app_fork_child_handler (void)
618 {
619   vcl_worker_t *parent_wrk;
620   int rv, parent_wrk_index;
621   u8 *child_name;
622
623   parent_wrk_index = vcl_get_worker_index ();
624   VDBG (0, "initializing forked child %u with parent wrk %u", getpid (),
625         parent_wrk_index);
626
627   /*
628    * Allocate worker
629    */
630   vcl_set_worker_index (~0);
631   if (!vcl_worker_alloc_and_init ())
632     VERR ("couldn't allocate new worker");
633
634   /*
635    * Attach to binary api
636    */
637   child_name = format (0, "%v-child-%u%c", vcm->app_name, getpid (), 0);
638   vcl_cleanup_bapi ();
639   vppcom_api_hookup ();
640   vcm->app_state = STATE_APP_START;
641   rv = vppcom_connect_to_vpp ((char *) child_name);
642   vec_free (child_name);
643   if (rv)
644     {
645       VERR ("couldn't connect to VPP!");
646       return;
647     }
648
649   /*
650    * Register worker with vpp and share sessions
651    */
652   vcl_worker_register_with_vpp ();
653   parent_wrk = vcl_worker_get (parent_wrk_index);
654   vls_worker_copy_on_fork (parent_wrk);
655   parent_wrk->forked_child = vcl_get_worker_index ();
656
657   VDBG (0, "forked child main worker initialized");
658   vcm->forking = 0;
659 }
660
661 static void
662 vls_app_fork_parent_handler (void)
663 {
664   vcm->forking = 1;
665   while (vcm->forking)
666     ;
667 }
668
669 int
670 vls_app_create (char *app_name)
671 {
672   int rv;
673   if ((rv = vppcom_app_create (app_name)))
674     return rv;
675   clib_rwlock_init (&vlsm->vls_table_lock);
676   pthread_atfork (vls_app_pre_fork, vls_app_fork_parent_handler,
677                   vls_app_fork_child_handler);
678   return VPPCOM_OK;
679 }
680
681 /*
682  * fd.io coding-style-patch-verification: ON
683  *
684  * Local Variables:
685  * eval: (c-set-style "gnu")
686  * End:
687  */