VPP-1335 vapi crash when memclnt_keepalive received
[vpp.git] / src / vpp-api / vapi / vapi.hpp
1 /*
2  *------------------------------------------------------------------
3  * Copyright (c) 2017 Cisco and/or its affiliates.
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at:
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *------------------------------------------------------------------
16  */
17
18 #ifndef vapi_hpp_included
19 #define vapi_hpp_included
20
21 #include <cstddef>
22 #include <vector>
23 #include <mutex>
24 #include <queue>
25 #include <cassert>
26 #include <functional>
27 #include <algorithm>
28 #include <atomic>
29 #include <vppinfra/types.h>
30 #include <vapi/vapi.h>
31 #include <vapi/vapi_internal.h>
32 #include <vapi/vapi_dbg.h>
33 #include <vapi/vpe.api.vapi.h>
34
35 #if VAPI_CPP_DEBUG_LEAKS
36 #include <unordered_set>
37 #endif
38
39 /**
40  * @file
41  * @brief C++ VPP API
42  */
43
44 namespace vapi
45 {
46
47 class Connection;
48
49 template <typename Req, typename Resp, typename... Args> class Request;
50 template <typename M> class Msg;
51 template <typename M> void vapi_swap_to_be (M *msg);
52 template <typename M> void vapi_swap_to_host (M *msg);
53 template <typename M, typename... Args>
54 M *vapi_alloc (Connection &con, Args...);
55 template <typename M> vapi_msg_id_t vapi_get_msg_id_t ();
56 template <typename M> class Event_registration;
57
58 class Unexpected_msg_id_exception : public std::exception
59 {
60 public:
61   virtual const char *what () const throw ()
62   {
63     return "unexpected message id";
64   }
65 };
66
67 class Msg_not_available_exception : public std::exception
68 {
69 public:
70   virtual const char *what () const throw ()
71   {
72     return "message unavailable";
73   }
74 };
75
76 typedef enum {
77   /** response not ready yet */
78   RESPONSE_NOT_READY,
79
80   /** response to request is ready */
81   RESPONSE_READY,
82
83   /** no response to request (will never come) */
84   RESPONSE_NO_RESPONSE,
85 } vapi_response_state_e;
86
87 /**
88  * Class representing common functionality of a request - response state
89  * and context
90  */
91 class Common_req
92 {
93 public:
94   virtual ~Common_req (){};
95
96   Connection &get_connection ()
97   {
98     return con;
99   };
100
101   vapi_response_state_e get_response_state (void) const
102   {
103     return response_state;
104   }
105
106 private:
107   Connection &con;
108   Common_req (Connection &con)
109       : con (con), context{0}, response_state{RESPONSE_NOT_READY}
110   {
111   }
112
113   void set_response_state (vapi_response_state_e state)
114   {
115     response_state = state;
116   }
117
118   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
119                                                           void *shm_data) = 0;
120
121   void set_context (u32 context)
122   {
123     this->context = context;
124   }
125
126   u32 get_context ()
127   {
128     return context;
129   }
130
131   u32 context;
132   vapi_response_state_e response_state;
133
134   friend class Connection;
135
136   template <typename M> friend class Msg;
137
138   template <typename Req, typename Resp, typename... Args>
139   friend class Request;
140
141   template <typename Req, typename Resp, typename... Args> friend class Dump;
142
143   template <typename M> friend class Event_registration;
144 };
145
146 /**
147  * Class representing a connection to VPP
148  *
149  * After creating a Connection object, call connect() to actually connect
150  * to VPP. Use is_msg_available to discover whether a specific message is known
151  * and supported by the VPP connected to.
152  */
153 class Connection
154 {
155 public:
156   Connection (void) : vapi_ctx{0}, event_count{0}
157   {
158
159     vapi_error_e rv = VAPI_OK;
160     if (!vapi_ctx)
161       {
162         if (VAPI_OK != (rv = vapi_ctx_alloc (&vapi_ctx)))
163           {
164             throw std::bad_alloc ();
165           }
166       }
167     events.reserve (vapi_get_message_count () + 1);
168   }
169
170   Connection (const Connection &) = delete;
171
172   ~Connection (void)
173   {
174     vapi_ctx_free (vapi_ctx);
175 #if VAPI_CPP_DEBUG_LEAKS
176     for (auto x : shm_data_set)
177       {
178         printf ("Leaked shm_data@%p!\n", x);
179       }
180 #endif
181   }
182
183   /**
184    * @brief check if message identified by it's message id is known by the
185    * vpp to which the connection is open
186    */
187   bool is_msg_available (vapi_msg_id_t type)
188   {
189     return vapi_is_msg_available (vapi_ctx, type);
190   }
191
192   /**
193    * @brief connect to vpp
194    *
195    * @param name application name
196    * @param chroot_prefix shared memory prefix
197    * @param max_queued_request max number of outstanding requests queued
198    * @param handle_keepalives handle memclnt_keepalive automatically
199    *
200    * @return VAPI_OK on success, other error code on error
201    */
202   vapi_error_e connect (const char *name, const char *chroot_prefix,
203                         int max_outstanding_requests, int response_queue_size,
204                         bool handle_keepalives = true)
205   {
206     return vapi_connect (vapi_ctx, name, chroot_prefix,
207                          max_outstanding_requests, response_queue_size,
208                          VAPI_MODE_BLOCKING, handle_keepalives);
209   }
210
211   /**
212    * @brief disconnect from vpp
213    *
214    * @return VAPI_OK on success, other error code on error
215    */
216   vapi_error_e disconnect ()
217   {
218     auto x = requests.size ();
219     while (x > 0)
220       {
221         VAPI_DBG ("popping request @%p", requests.front ());
222         requests.pop_front ();
223         --x;
224       }
225     return vapi_disconnect (vapi_ctx);
226   };
227
228   /**
229    * @brief get event file descriptor
230    *
231    * @note this file descriptor becomes readable when messages (from vpp)
232    * are waiting in queue
233    *
234    * @param[out] fd pointer to result variable
235    *
236    * @return VAPI_OK on success, other error code on error
237    */
238   vapi_error_e get_fd (int *fd)
239   {
240     return vapi_get_fd (vapi_ctx, fd);
241   }
242
243   /**
244    * @brief wait for responses from vpp and assign them to appropriate objects
245    *
246    * @param limit stop dispatch after the limit object received it's response
247    *
248    * @return VAPI_OK on success, other error code on error
249    */
250   vapi_error_e dispatch (const Common_req *limit = nullptr, u32 time = 5)
251   {
252     std::lock_guard<std::mutex> lock (dispatch_mutex);
253     vapi_error_e rv = VAPI_OK;
254     bool loop_again = true;
255     while (loop_again)
256       {
257         void *shm_data;
258         size_t shm_data_size;
259         rv = vapi_recv (vapi_ctx, &shm_data, &shm_data_size, SVM_Q_TIMEDWAIT,
260                         time);
261         if (VAPI_OK != rv)
262           {
263             return rv;
264           }
265 #if VAPI_CPP_DEBUG_LEAKS
266         on_shm_data_alloc (shm_data);
267 #endif
268         std::lock_guard<std::recursive_mutex> requests_lock (requests_mutex);
269         std::lock_guard<std::recursive_mutex> events_lock (events_mutex);
270         vapi_msg_id_t id = vapi_lookup_vapi_msg_id_t (
271             vapi_ctx, be16toh (*static_cast<u16 *> (shm_data)));
272         bool has_context = vapi_msg_is_with_context (id);
273         bool break_dispatch = false;
274         Common_req *matching_req = nullptr;
275         if (has_context)
276           {
277             u32 context = *reinterpret_cast<u32 *> (
278                 (static_cast<u8 *> (shm_data) + vapi_get_context_offset (id)));
279             const auto x = requests.front ();
280             matching_req = x;
281             if (context == x->context)
282               {
283                 std::tie (rv, break_dispatch) =
284                     x->assign_response (id, shm_data);
285               }
286             else
287               {
288                 std::tie (rv, break_dispatch) =
289                     x->assign_response (id, nullptr);
290               }
291             if (break_dispatch)
292               {
293                 requests.pop_front ();
294               }
295           }
296         else
297           {
298             if (events[id])
299               {
300                 std::tie (rv, break_dispatch) =
301                     events[id]->assign_response (id, shm_data);
302                 matching_req = events[id];
303               }
304             else
305               {
306                 msg_free (shm_data);
307               }
308           }
309         if ((matching_req && matching_req == limit && break_dispatch) ||
310             VAPI_OK != rv)
311           {
312             return rv;
313           }
314         loop_again = !requests.empty () || (event_count > 0);
315       }
316     return rv;
317   }
318
319   /**
320    * @brief convenience wrapper function
321    */
322   vapi_error_e dispatch (const Common_req &limit)
323   {
324     return dispatch (&limit);
325   }
326
327   /**
328    * @brief wait for response to a specific request
329    *
330    * @param req request to wait for response for
331    *
332    * @return VAPI_OK on success, other error code on error
333    */
334   vapi_error_e wait_for_response (const Common_req &req)
335   {
336     if (RESPONSE_READY == req.get_response_state ())
337       {
338         return VAPI_OK;
339       }
340     return dispatch (req);
341   }
342
343 private:
344   void msg_free (void *shm_data)
345   {
346 #if VAPI_CPP_DEBUG_LEAKS
347     on_shm_data_free (shm_data);
348 #endif
349     vapi_msg_free (vapi_ctx, shm_data);
350   }
351
352   template <template <typename XReq, typename XResp, typename... XArgs>
353             class X,
354             typename Req, typename Resp, typename... Args>
355   vapi_error_e send (X<Req, Resp, Args...> *req)
356   {
357     if (!req)
358       {
359         return VAPI_EINVAL;
360       }
361     u32 req_context =
362         req_context_counter.fetch_add (1, std::memory_order_relaxed);
363     req->request.shm_data->header.context = req_context;
364     vapi_swap_to_be<Req> (req->request.shm_data);
365     std::lock_guard<std::recursive_mutex> lock (requests_mutex);
366     vapi_error_e rv = vapi_send (vapi_ctx, req->request.shm_data);
367     if (VAPI_OK == rv)
368       {
369         VAPI_DBG ("Push %p", req);
370         requests.emplace_back (req);
371         req->set_context (req_context);
372 #if VAPI_CPP_DEBUG_LEAKS
373         on_shm_data_free (req->request.shm_data);
374 #endif
375         req->request.shm_data = nullptr; /* consumed by vapi_send */
376       }
377     else
378       {
379         vapi_swap_to_host<Req> (req->request.shm_data);
380       }
381     return rv;
382   }
383
384   template <template <typename XReq, typename XResp, typename... XArgs>
385             class X,
386             typename Req, typename Resp, typename... Args>
387   vapi_error_e send_with_control_ping (X<Req, Resp, Args...> *req)
388   {
389     if (!req)
390       {
391         return VAPI_EINVAL;
392       }
393     u32 req_context =
394         req_context_counter.fetch_add (1, std::memory_order_relaxed);
395     req->request.shm_data->header.context = req_context;
396     vapi_swap_to_be<Req> (req->request.shm_data);
397     std::lock_guard<std::recursive_mutex> lock (requests_mutex);
398     vapi_error_e rv = vapi_send_with_control_ping (
399         vapi_ctx, req->request.shm_data, req_context);
400     if (VAPI_OK == rv)
401       {
402         VAPI_DBG ("Push %p", req);
403         requests.emplace_back (req);
404         req->set_context (req_context);
405 #if VAPI_CPP_DEBUG_LEAKS
406         on_shm_data_free (req->request.shm_data);
407 #endif
408         req->request.shm_data = nullptr; /* consumed by vapi_send */
409       }
410     else
411       {
412         vapi_swap_to_host<Req> (req->request.shm_data);
413       }
414     return rv;
415   }
416
417   void unregister_request (Common_req *request)
418   {
419     std::lock_guard<std::recursive_mutex> lock (requests_mutex);
420     std::remove (requests.begin (), requests.end (), request);
421   }
422
423   template <typename M> void register_event (Event_registration<M> *event)
424   {
425     const vapi_msg_id_t id = M::get_msg_id ();
426     std::lock_guard<std::recursive_mutex> lock (events_mutex);
427     events[id] = event;
428     ++event_count;
429   }
430
431   template <typename M> void unregister_event (Event_registration<M> *event)
432   {
433     const vapi_msg_id_t id = M::get_msg_id ();
434     std::lock_guard<std::recursive_mutex> lock (events_mutex);
435     events[id] = nullptr;
436     --event_count;
437   }
438
439   vapi_ctx_t vapi_ctx;
440   std::atomic_ulong req_context_counter;
441   std::mutex dispatch_mutex;
442
443   std::recursive_mutex requests_mutex;
444   std::recursive_mutex events_mutex;
445   std::deque<Common_req *> requests;
446   std::vector<Common_req *> events;
447   int event_count;
448
449   template <typename Req, typename Resp, typename... Args>
450   friend class Request;
451
452   template <typename Req, typename Resp, typename... Args> friend class Dump;
453
454   template <typename M> friend class Result_set;
455
456   template <typename M> friend class Event_registration;
457
458   template <typename M, typename... Args>
459   friend M *vapi_alloc (Connection &con, Args...);
460
461   template <typename M> friend class Msg;
462
463 #if VAPI_CPP_DEBUG_LEAKS
464   void on_shm_data_alloc (void *shm_data)
465   {
466     if (shm_data)
467       {
468         auto pos = shm_data_set.find (shm_data);
469         if (pos == shm_data_set.end ())
470           {
471             shm_data_set.insert (shm_data);
472           }
473         else
474           {
475             printf ("Double-add shm_data @%p!\n", shm_data);
476           }
477       }
478   }
479
480   void on_shm_data_free (void *shm_data)
481   {
482     auto pos = shm_data_set.find (shm_data);
483     if (pos == shm_data_set.end ())
484       {
485         printf ("Freeing untracked shm_data @%p!\n", shm_data);
486       }
487     else
488       {
489         shm_data_set.erase (pos);
490       }
491   }
492   std::unordered_set<void *> shm_data_set;
493 #endif
494 };
495
496 template <typename Req, typename Resp, typename... Args> class Request;
497
498 template <typename Req, typename Resp, typename... Args> class Dump;
499
500 template <class, class = void> struct vapi_has_payload_trait : std::false_type
501 {
502 };
503
504 template <class... T> using vapi_void_t = void;
505
506 template <class T>
507 struct vapi_has_payload_trait<T, vapi_void_t<decltype (&T::payload)>>
508     : std::true_type
509 {
510 };
511
512 template <typename M> void vapi_msg_set_msg_id (vapi_msg_id_t id)
513 {
514   Msg<M>::set_msg_id (id);
515 }
516
517 /**
518  * Class representing a message stored in shared memory
519  */
520 template <typename M> class Msg
521 {
522 public:
523   Msg (const Msg &) = delete;
524
525   ~Msg ()
526   {
527     VAPI_DBG ("Destroy Msg<%s>@%p, shm_data@%p",
528               vapi_get_msg_name (get_msg_id ()), this, shm_data);
529     if (shm_data)
530       {
531         con.get ().msg_free (shm_data);
532         shm_data = nullptr;
533       }
534   }
535
536   static vapi_msg_id_t get_msg_id ()
537   {
538     return *msg_id_holder ();
539   }
540
541   template <typename X = M>
542   typename std::enable_if<vapi_has_payload_trait<X>::value,
543                           decltype (X::payload) &>::type
544   get_payload () const
545   {
546     return shm_data->payload;
547   }
548
549 private:
550   Msg (Msg<M> &&msg) : con{msg.con}
551   {
552     VAPI_DBG ("Move construct Msg<%s> from msg@%p to msg@%p, shm_data@%p",
553               vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data);
554     shm_data = msg.shm_data;
555     msg.shm_data = nullptr;
556   }
557
558   Msg<M> &operator= (Msg<M> &&msg)
559   {
560     VAPI_DBG ("Move assign Msg<%s> from msg@%p to msg@%p, shm_data@%p",
561               vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data);
562     con.get ().msg_free (shm_data);
563     con = msg.con;
564     shm_data = msg.shm_data;
565     msg.shm_data = nullptr;
566     return *this;
567   }
568
569   struct Msg_allocator : std::allocator<Msg<M>>
570   {
571     template <class U, class... Args> void construct (U *p, Args &&... args)
572     {
573       ::new ((void *)p) U (std::forward<Args> (args)...);
574     }
575
576     template <class U> struct rebind
577     {
578       typedef Msg_allocator other;
579     };
580   };
581
582   static void set_msg_id (vapi_msg_id_t id)
583   {
584     assert ((VAPI_INVALID_MSG_ID == *msg_id_holder ()) ||
585             (id == *msg_id_holder ()));
586     *msg_id_holder () = id;
587   }
588
589   static vapi_msg_id_t *msg_id_holder ()
590   {
591     static vapi_msg_id_t my_id{VAPI_INVALID_MSG_ID};
592     return &my_id;
593   }
594
595   Msg (Connection &con, void *shm_data) : con{con}
596   {
597     if (!con.is_msg_available (get_msg_id ()))
598       {
599         throw Msg_not_available_exception ();
600       }
601     this->shm_data = static_cast<shm_data_type *> (shm_data);
602     VAPI_DBG ("New Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()),
603               this, shm_data);
604   }
605
606   void assign_response (vapi_msg_id_t resp_id, void *shm_data)
607   {
608     assert (nullptr == this->shm_data);
609     if (resp_id != get_msg_id ())
610       {
611         throw Unexpected_msg_id_exception ();
612       }
613     this->shm_data = static_cast<M *> (shm_data);
614     vapi_swap_to_host<M> (this->shm_data);
615     VAPI_DBG ("Assign response to Msg<%s>@%p shm_data@%p",
616               vapi_get_msg_name (get_msg_id ()), this, shm_data);
617   }
618
619   std::reference_wrapper<Connection> con;
620   using shm_data_type = M;
621   shm_data_type *shm_data;
622
623   friend class Connection;
624
625   template <typename Req, typename Resp, typename... Args>
626   friend class Request;
627
628   template <typename Req, typename Resp, typename... Args> friend class Dump;
629
630   template <typename X> friend class Event_registration;
631
632   template <typename X> friend class Result_set;
633
634   friend struct Msg_allocator;
635
636   template <typename X> friend void vapi_msg_set_msg_id (vapi_msg_id_t id);
637 };
638
639 /**
640  * Class representing a simple request - with a single response message
641  */
642 template <typename Req, typename Resp, typename... Args>
643 class Request : public Common_req
644 {
645 public:
646   Request (Connection &con, Args... args,
647            std::function<vapi_error_e (Request<Req, Resp, Args...> &)>
648                callback = nullptr)
649       : Common_req{con}, callback{callback},
650         request{con, vapi_alloc<Req> (con, args...)}, response{con, nullptr}
651   {
652   }
653
654   Request (const Request &) = delete;
655
656   virtual ~Request ()
657   {
658     if (RESPONSE_NOT_READY == get_response_state ())
659       {
660         con.unregister_request (this);
661       }
662   }
663
664   vapi_error_e execute ()
665   {
666     return con.send (this);
667   }
668
669   const Msg<Req> &get_request (void) const
670   {
671     return request;
672   }
673
674   const Msg<Resp> &get_response (void)
675   {
676     return response;
677   }
678
679 private:
680   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
681                                                           void *shm_data)
682   {
683     assert (RESPONSE_NOT_READY == get_response_state ());
684     response.assign_response (id, shm_data);
685     set_response_state (RESPONSE_READY);
686     if (nullptr != callback)
687       {
688         return std::make_pair (callback (*this), true);
689       }
690     return std::make_pair (VAPI_OK, true);
691   }
692   std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback;
693   Msg<Req> request;
694   Msg<Resp> response;
695
696   friend class Connection;
697 };
698
699 /**
700  * Class representing iterable set of responses of the same type
701  */
702 template <typename M> class Result_set
703 {
704 public:
705   ~Result_set ()
706   {
707   }
708
709   Result_set (const Result_set &) = delete;
710
711   bool is_complete () const
712   {
713     return complete;
714   }
715
716   size_t size () const
717   {
718     return set.size ();
719   }
720
721   using const_iterator =
722       typename std::vector<Msg<M>,
723                            typename Msg<M>::Msg_allocator>::const_iterator;
724
725   const_iterator begin () const
726   {
727     return set.begin ();
728   }
729
730   const_iterator end () const
731   {
732     return set.end ();
733   }
734
735   void free_response (const_iterator pos)
736   {
737     set.erase (pos);
738   }
739
740   void free_all_responses ()
741   {
742     set.clear ();
743   }
744
745 private:
746   void mark_complete ()
747   {
748     complete = true;
749   }
750
751   void assign_response (vapi_msg_id_t resp_id, void *shm_data)
752   {
753     if (resp_id != Msg<M>::get_msg_id ())
754       {
755         {
756           throw Unexpected_msg_id_exception ();
757         }
758       }
759     else if (shm_data)
760       {
761         vapi_swap_to_host<M> (static_cast<M *> (shm_data));
762         set.emplace_back (con, shm_data);
763         VAPI_DBG ("Result_set@%p emplace_back shm_data@%p", this, shm_data);
764       }
765   }
766
767   Result_set (Connection &con) : con (con), complete{false}
768   {
769   }
770
771   Connection &con;
772   bool complete;
773   std::vector<Msg<M>, typename Msg<M>::Msg_allocator> set;
774
775   template <typename Req, typename Resp, typename... Args> friend class Dump;
776
777   template <typename X> friend class Event_registration;
778 };
779
780 /**
781  * Class representing a dump request - zero or more identical responses to a
782  * single request message
783  */
784 template <typename Req, typename Resp, typename... Args>
785 class Dump : public Common_req
786 {
787 public:
788   Dump (Connection &con, Args... args,
789         std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback =
790             nullptr)
791       : Common_req{con}, request{con, vapi_alloc<Req> (con, args...)},
792         result_set{con}, callback{callback}
793   {
794   }
795
796   Dump (const Dump &) = delete;
797
798   virtual ~Dump ()
799   {
800   }
801
802   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
803                                                           void *shm_data)
804   {
805     if (id == vapi_msg_id_control_ping_reply)
806       {
807         con.msg_free (shm_data);
808         result_set.mark_complete ();
809         set_response_state (RESPONSE_READY);
810         if (nullptr != callback)
811           {
812             return std::make_pair (callback (*this), true);
813           }
814         return std::make_pair (VAPI_OK, true);
815       }
816     else
817       {
818         result_set.assign_response (id, shm_data);
819       }
820     return std::make_pair (VAPI_OK, false);
821   }
822
823   vapi_error_e execute ()
824   {
825     return con.send_with_control_ping (this);
826   }
827
828   Msg<Req> &get_request (void)
829   {
830     return request;
831   }
832
833   using resp_type = typename Msg<Resp>::shm_data_type;
834
835   const Result_set<Resp> &get_result_set (void) const
836   {
837     return result_set;
838   }
839
840 private:
841   Msg<Req> request;
842   Result_set<resp_type> result_set;
843   std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback;
844
845   friend class Connection;
846 };
847
848 /**
849  * Class representing event registration - incoming events (messages) from
850  * vpp as a result of a subscription (typically a want_* simple request)
851  */
852 template <typename M> class Event_registration : public Common_req
853 {
854 public:
855   Event_registration (
856       Connection &con,
857       std::function<vapi_error_e (Event_registration<M> &)> callback = nullptr)
858       : Common_req{con}, result_set{con}, callback{callback}
859   {
860     if (!con.is_msg_available (M::get_msg_id ()))
861       {
862         throw Msg_not_available_exception ();
863       }
864     con.register_event (this);
865   }
866
867   Event_registration (const Event_registration &) = delete;
868
869   virtual ~Event_registration ()
870   {
871     con.unregister_event (this);
872   }
873
874   virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id,
875                                                           void *shm_data)
876   {
877     result_set.assign_response (id, shm_data);
878     if (nullptr != callback)
879       {
880         return std::make_pair (callback (*this), true);
881       }
882     return std::make_pair (VAPI_OK, true);
883   }
884
885   using resp_type = typename M::shm_data_type;
886
887   Result_set<resp_type> &get_result_set (void)
888   {
889     return result_set;
890   }
891
892 private:
893   Result_set<resp_type> result_set;
894   std::function<vapi_error_e (Event_registration<M> &)> callback;
895 };
896 };
897
898 #endif
899
900 /*
901  * fd.io coding-style-patch-verification: ON
902  *
903  * Local Variables:
904  * eval: (c-set-style "gnu")
905  * End:
906  */