-lockmanager acquire retry
[oweals/gnunet.git] / src / lockmanager / lockmanager_api.c
1 /*
2      This file is part of GNUnet.
3      (C) 2012 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 2, or (at your
8      option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file lockmanager/lockmanager_api.c
23  * @brief API implementation of gnunet_lockmanager_service.h
24  * @author Sree Harsha Totakura
25  */
26
27 /**
28  * To be fixed:
29  *  Should the handle be freed when the connection to service is lost?
30  *  Should cancel_request have a call back (else simultaneous calls break)
31  */
32
33 #include "platform.h"
34 #include "gnunet_common.h"
35 #include "gnunet_container_lib.h"
36 #include "gnunet_client_lib.h"
37 #include "gnunet_crypto_lib.h"
38 #include "gnunet_lockmanager_service.h"
39 #include "gnunet_protocols.h"
40
41 #include "lockmanager.h"
42
43 #define LOG(kind,...) \
44   GNUNET_log_from (kind, "lockmanager-api",__VA_ARGS__)
45
46 #define TIME_REL_MINS(min) \
47   GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, min)
48
49 #define TIMEOUT TIME_REL_MINS(3)
50
51
52 /**
53  * The message queue
54  */
55 struct MessageQueue
56 {
57   /**
58    * The next pointer for doubly linked list
59    */
60   struct MessageQueue *next;
61
62   /**
63    * The prev pointer for doubly linked list
64    */
65   struct MessageQueue *prev;
66   
67   /**
68    * The LOCKMANAGER Message
69    */
70   struct GNUNET_LOCKMANAGER_Message *msg;
71 };
72
73
74 /**
75  * Handler for the lockmanager service
76  */
77 struct GNUNET_LOCKMANAGER_Handle
78 {
79   /**
80    * The client connection to the service
81    */
82   struct GNUNET_CLIENT_Connection *conn;
83
84   /**
85    * The transmit handle for transmissions using conn
86    */
87   struct GNUNET_CLIENT_TransmitHandle *transmit_handle;
88
89   /**
90    * Hashmap handle
91    */
92   struct GNUNET_CONTAINER_MultiHashMap *hashmap;
93
94   /**
95    * Double linked list head for message queue
96    */
97   struct MessageQueue *mq_head;
98
99   /**
100    * Double linked list tail for message queue
101    */
102   struct MessageQueue *mq_tail;
103
104   /**
105    * Are we currently handling replies?
106    */
107   int in_replies;
108 };
109
110
111 /**
112  * Structure for Locking Request
113  */
114 struct GNUNET_LOCKMANAGER_LockingRequest
115 {
116   /**
117    * The handle associated with this request
118    */
119   struct GNUNET_LOCKMANAGER_Handle *handle;
120
121   /**
122    * The status callback
123    */
124   GNUNET_LOCKMANAGER_StatusCallback status_cb;
125
126   /**
127    * Closure for the status callback
128    */
129   void *status_cb_cls;
130
131   /**
132    * The locking domain of this request
133    */
134   char *domain;
135   
136   /**
137    * The lock
138    */
139   uint32_t lock;
140
141   /**
142    * The status of the lock
143    */
144   enum GNUNET_LOCKMANAGER_Status status;
145 };
146
147
148 /**
149  * Structure for matching a lock
150  */
151 struct LockingRequestMatch
152 {
153   /**
154    * The matched LockingRequest entry; Should be NULL if no entry is found
155    */
156   struct GNUNET_LOCKMANAGER_LockingRequest *matched_entry;
157
158   /**
159    * The locking domain name of the lock
160    */
161   const char *domain;
162
163   /**
164    * The lock number
165    */
166   uint32_t lock;
167 };
168
169
170 /**
171  * Handler for server replies
172  *
173  * @param cls the LOCKMANAGER_Handle
174  * @param msg received message, NULL on timeout or fatal error
175  */
176 static void 
177 handle_replies (void *cls,
178                 const struct GNUNET_MessageHeader *msg);
179
180
181 /**
182  * Transmit notify for sending message to server
183  *
184  * @param cls the lockmanager handle
185  * @param size number of bytes available in buf
186  * @param buf where the callee should write the message
187  * @return number of bytes written to buf
188  */
189 static size_t 
190 transmit_notify (void *cls, size_t size, void *buf)
191 {
192   struct GNUNET_LOCKMANAGER_Handle *handle = cls;
193   struct MessageQueue *queue_entity;
194   uint16_t msg_size;
195
196   handle->transmit_handle = NULL;
197   queue_entity = handle->mq_head;
198   GNUNET_assert (NULL != queue_entity);
199   if ((0 == size) || (NULL == buf))
200   {
201     handle->transmit_handle =
202       GNUNET_CLIENT_notify_transmit_ready (handle->conn,
203                                            ntohs
204                                            (queue_entity->msg->header.size),
205                                            GNUNET_TIME_UNIT_FOREVER_REL,
206                                            GNUNET_YES,
207                                            &transmit_notify,
208                                            handle);
209     return 0;
210   } 
211   msg_size = ntohs (queue_entity->msg->header.size);
212   GNUNET_assert (size >= msg_size);
213   memcpy (buf, queue_entity->msg, msg_size);
214   LOG (GNUNET_ERROR_TYPE_DEBUG,
215        "Message of size %u sent\n", msg_size);
216   GNUNET_free (queue_entity->msg);
217   GNUNET_CONTAINER_DLL_remove (handle->mq_head,
218                                handle->mq_tail,
219                                queue_entity);
220   GNUNET_free (queue_entity);
221   queue_entity = handle->mq_head;
222   if (NULL != queue_entity)
223   {
224     handle->transmit_handle =
225       GNUNET_CLIENT_notify_transmit_ready (handle->conn,
226                                            ntohs
227                                            (queue_entity->msg->header.size),
228                                            TIMEOUT,
229                                            GNUNET_YES,
230                                            &transmit_notify,
231                                            handle);
232   }
233   if (GNUNET_NO == handle->in_replies)
234   {
235     GNUNET_CLIENT_receive (handle->conn,
236                            &handle_replies,
237                            handle,
238                            GNUNET_TIME_UNIT_FOREVER_REL);
239     handle->in_replies = GNUNET_YES;
240   }
241   return msg_size;
242 }
243
244
245 /**
246  * Queues a message into handle's send message queue
247  *
248  * @param handle the lockmanager handle whose queue will be used
249  * @param msg the message to be queued
250  */
251 static void
252 queue_message (struct GNUNET_LOCKMANAGER_Handle *handle,
253                struct GNUNET_LOCKMANAGER_Message *msg)
254 {
255   struct MessageQueue *queue_entity;
256
257   GNUNET_assert (NULL != msg);
258   queue_entity = GNUNET_malloc (sizeof (struct MessageQueue));
259   queue_entity->msg = msg;
260   GNUNET_CONTAINER_DLL_insert_tail (handle->mq_head,
261                                     handle->mq_tail,
262                                     queue_entity);
263   if (NULL == handle->transmit_handle)
264   {
265     handle->transmit_handle =
266       GNUNET_CLIENT_notify_transmit_ready (handle->conn,
267                                            ntohs (msg->header.size),
268                                            TIMEOUT,
269                                            GNUNET_YES,
270                                            &transmit_notify,
271                                            handle);
272   }
273 }
274
275
276 /**
277  * Get the key for the given lock in the 'lock_map'.
278  *
279  * @param domain_name
280  * @param lock_number
281  * @param key set to the key
282  */
283 static void
284 get_key (const char *domain_name,
285          uint32_t lock_number,
286          struct GNUNET_HashCode *key)
287 {
288   uint32_t *last_32;
289
290   GNUNET_CRYPTO_hash (domain_name,
291                       strlen (domain_name),
292                       key);
293   last_32 = (uint32_t *) key;
294   *last_32 ^= lock_number;
295 }
296
297
298 /**
299  * Hashmap iterator for matching a LockingRequest
300  *
301  * @param cls the LockingRequestMatch structure
302  * @param key current key code
303  * @param value value in the hash map (struct GNUNET_LOCKMANAGER_LockingRequest)
304  * @return GNUNET_YES if we should continue to
305  *         iterate,
306  *         GNUNET_NO if not. 
307  */
308 static int
309 match_iterator (void *cls, const struct GNUNET_HashCode *key, void *value)
310 {
311   struct LockingRequestMatch *match = cls;
312   struct GNUNET_LOCKMANAGER_LockingRequest *lr = value;
313
314   if ( (match->lock == lr->lock) && (0 == strcmp (match->domain, lr->domain)) )
315   {
316     match->matched_entry = lr;    
317     return GNUNET_NO;
318   }
319   return GNUNET_YES;
320 }
321
322
323 /**
324  * Function to find a LockingRequest associated with the given domain and lock
325  * attributes in the map
326  *
327  * @param map the map where the LockingRequests are stored
328  * @param domain the locking domain name
329  * @param lock the lock number
330  * @return the found LockingRequest; NULL if a matching LockingRequest wasn't
331  *           found 
332  */
333 static struct GNUNET_LOCKMANAGER_LockingRequest *
334 hashmap_find_lockingrequest (const struct GNUNET_CONTAINER_MultiHashMap *map,
335                              const char *domain,
336                              uint32_t lock)
337 {
338   struct GNUNET_HashCode hash;
339   struct LockingRequestMatch lock_match;
340
341   lock_match.matched_entry = NULL;
342   lock_match.domain = domain;
343   lock_match.lock = lock;
344   get_key (domain, lock, &hash);
345   GNUNET_CONTAINER_multihashmap_get_multiple (map,
346                                               &hash,
347                                               &match_iterator,
348                                               &lock_match);
349   return lock_match.matched_entry;
350 }
351
352
353 /**
354  * Task for calling status change callback for a lock
355  *
356  * @param cls the LockingRequest associated with this lock
357  * @param tc the TaskScheduler context
358  */
359 static void
360 call_status_cb_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
361 {
362   const struct GNUNET_LOCKMANAGER_LockingRequest *r = cls;
363
364   if (NULL != r->status_cb)
365   {
366     LOG (GNUNET_ERROR_TYPE_DEBUG,
367          "Calling status change for SUCCESS on lock num: %d, domain: %s\n",
368          r->lock, r->domain);
369     r->status_cb (r->status_cb_cls,
370                   r->domain,
371                   r->lock,
372                   r->status);
373   }
374 }
375
376
377 /**
378  * Function to generate acquire message for a lock
379  *
380  * @param domain_name the domain name of the lock
381  * @param lock the lock number
382  * @return the generated GNUNET_LOCKMANAGER_Message
383  */
384 static struct GNUNET_LOCKMANAGER_Message *
385 generate_acquire_msg (const char *domain_name, uint32_t lock)
386 {
387   struct GNUNET_LOCKMANAGER_Message *msg;
388   size_t domain_name_len;
389   uint16_t  msg_size;
390   
391   domain_name_len = strlen (domain_name) + 1;
392   msg_size = sizeof (struct GNUNET_LOCKMANAGER_Message) + domain_name_len;
393   msg = GNUNET_malloc (msg_size);
394   msg->header.type = htons (GNUNET_MESSAGE_TYPE_LOCKMANAGER_ACQUIRE);
395   msg->header.size = htons (msg_size);
396   msg->lock = htonl (lock);
397   memcpy (&msg[1], domain_name, domain_name_len);
398   return msg;
399 }
400
401
402 /**
403  * Iterator to call relase on locks
404  *
405  * @param cls the lockmanager handle
406  * @param key current key code
407  * @param value the Locking request
408  * @return GNUNET_YES if we should continue to
409  *         iterate,
410  *         GNUNET_NO if not.
411  */
412 static int
413 release_n_retry_iterator (void *cls,
414                           const struct GNUNET_HashCode * key,
415                           void *value)
416 {
417   struct GNUNET_LOCKMANAGER_LockingRequest *r = value;
418   struct GNUNET_LOCKMANAGER_Handle *h = cls;
419   struct GNUNET_LOCKMANAGER_Message *msg;
420
421   msg = generate_acquire_msg (r->domain, r->lock);
422   queue_message (h, msg);
423   if (GNUNET_LOCKMANAGER_RELEASE == r->status)
424     return GNUNET_YES;
425   if (NULL != r->status_cb)
426   {
427     LOG (GNUNET_ERROR_TYPE_DEBUG,
428          "Calling status change for RELEASE on lock num: %d, domain: %s\n",
429          r->lock, r->domain);
430     r->status = GNUNET_LOCKMANAGER_RELEASE;
431     r->status_cb (r->status_cb_cls,
432                   r->domain,
433                   r->lock,
434                   GNUNET_LOCKMANAGER_RELEASE);
435   }
436   return GNUNET_YES;
437 }
438
439
440 /**
441  * Handler for server replies
442  *
443  * @param cls the LOCKMANAGER_Handle
444  * @param msg received message, NULL on timeout or fatal error
445  */
446 static void 
447 handle_replies (void *cls,
448                 const struct GNUNET_MessageHeader *msg)
449 {
450   struct GNUNET_LOCKMANAGER_Handle *handle = cls;
451   const struct GNUNET_LOCKMANAGER_Message *m;
452   struct GNUNET_LOCKMANAGER_LockingRequest *lr;
453   const char *domain;
454   struct GNUNET_HashCode hash;
455   uint32_t lock;
456   uint16_t msize;
457
458   handle->in_replies = GNUNET_NO;
459   if (NULL == msg)
460   {
461     LOG (GNUNET_ERROR_TYPE_DEBUG,
462          "Lockmanager service not available or went down\n");    
463     /* Should release all locks and retry to acquire them */
464     GNUNET_CONTAINER_multihashmap_iterate (handle->hashmap,
465                                            &release_n_retry_iterator,
466                                            handle);
467     return;
468   }
469   GNUNET_CLIENT_receive (handle->conn,
470                          &handle_replies,
471                          handle,
472                          GNUNET_TIME_UNIT_FOREVER_REL);
473   handle->in_replies = GNUNET_YES;
474   if (GNUNET_MESSAGE_TYPE_LOCKMANAGER_SUCCESS != ntohs(msg->type))
475   {
476     GNUNET_break (0);
477     return;
478   }
479   msize = ntohs (msg->size);
480   if (msize <= sizeof (struct GNUNET_LOCKMANAGER_Message))
481   {
482     GNUNET_break (0);
483     return;
484   }
485   m = (const struct GNUNET_LOCKMANAGER_Message *) msg;
486   domain = (const char *) &m[1];
487   msize -= sizeof (struct GNUNET_LOCKMANAGER_Message);
488   if ('\0' != domain[msize-1])
489   {
490     GNUNET_break (0);
491     return;
492   }
493
494   lock = ntohl (m->lock);
495   get_key (domain, lock, &hash);      
496   LOG (GNUNET_ERROR_TYPE_DEBUG,
497        "Received SUCCESS message for lock: %d, domain %s\n",
498        lock, domain);
499   if (NULL == (lr = hashmap_find_lockingrequest (handle->hashmap,
500                                                  domain,
501                                                  lock)))
502   {
503     GNUNET_break (0);
504     return;
505   }
506   if (GNUNET_LOCKMANAGER_SUCCESS == lr->status)
507   {
508     GNUNET_break (0);
509     return;
510   }
511   LOG (GNUNET_ERROR_TYPE_DEBUG,
512        "Changing status for lock: %d in domain: %s to SUCCESS\n",
513        lr->lock, lr->domain);
514   lr->status = GNUNET_LOCKMANAGER_SUCCESS;
515   GNUNET_SCHEDULER_add_continuation (&call_status_cb_task,
516                                      lr,
517                                      GNUNET_SCHEDULER_REASON_PREREQ_DONE);
518 }
519
520
521 /**
522  * Iterator to free hash map entries.
523  *
524  * @param cls the lockmanger handle
525  * @param key current key code
526  * @param value the Locking request
527  * @return GNUNET_YES if we should continue to
528  *         iterate,
529  *         GNUNET_NO if not.
530  */
531 static int
532 free_iterator(void *cls,
533               const struct GNUNET_HashCode * key,
534               void *value)
535 {
536   struct GNUNET_LOCKMANAGER_Handle *h = cls;
537   struct GNUNET_LOCKMANAGER_LockingRequest *r = value;
538
539   LOG (GNUNET_ERROR_TYPE_DEBUG,
540        "Clearing locking request\n");
541   GNUNET_assert (GNUNET_YES == 
542                  GNUNET_CONTAINER_multihashmap_remove (h->hashmap,
543                                                        key,
544                                                        value));
545   GNUNET_free (r->domain);
546   GNUNET_free (r);
547   return GNUNET_YES;
548 }
549
550
551 /*******************/
552 /* API Definitions */
553 /*******************/
554
555
556 /**
557  * Connect to the lockmanager service
558  *
559  * @param cfg the configuration to use
560  *
561  * @return upon success the handle to the service; NULL upon error
562  */
563 struct GNUNET_LOCKMANAGER_Handle *
564 GNUNET_LOCKMANAGER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg)
565 {
566   struct GNUNET_LOCKMANAGER_Handle *h;
567
568   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s()\n", __func__);
569   h = GNUNET_malloc (sizeof (struct GNUNET_LOCKMANAGER_Handle));
570   h->conn = GNUNET_CLIENT_connect ("lockmanager", cfg);
571   if (NULL == h->conn)
572   {
573     GNUNET_free (h);
574     LOG (GNUNET_ERROR_TYPE_DEBUG, "%s() END\n", __func__);
575     return NULL;
576   }  
577   h->hashmap = GNUNET_CONTAINER_multihashmap_create (15);
578   GNUNET_assert (NULL != h->hashmap);
579   GNUNET_CLIENT_receive (h->conn,
580                          &handle_replies,
581                          h,
582                          GNUNET_TIME_UNIT_FOREVER_REL);
583   h->in_replies = GNUNET_YES;
584   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s() END\n", __func__);
585   return h;
586 }
587
588
589 /**
590  * Disconnect from the lockmanager service
591  *
592  * @param handle the handle to the lockmanager service
593  */
594 void
595 GNUNET_LOCKMANAGER_disconnect (struct GNUNET_LOCKMANAGER_Handle *handle)
596 {
597   struct MessageQueue *head;
598
599   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s()\n", __func__);
600   if (0 != GNUNET_CONTAINER_multihashmap_size (handle->hashmap))
601   {
602     LOG (GNUNET_ERROR_TYPE_WARNING,
603          "Some locking requests are still present. Cancel them before "
604          "calling %s\n", __func__);
605     GNUNET_CONTAINER_multihashmap_iterate (handle->hashmap,
606                                            &free_iterator,
607                                            handle);
608   }
609   GNUNET_CONTAINER_multihashmap_destroy (handle->hashmap);
610   /* Clear the message queue */
611   if (NULL != handle->transmit_handle)
612   {
613     GNUNET_CLIENT_notify_transmit_ready_cancel (handle->transmit_handle);
614   }
615   head = handle->mq_head;
616   while (NULL != head)
617   {
618     GNUNET_CONTAINER_DLL_remove (handle->mq_head,
619                                  handle->mq_tail,
620                                  head);
621     GNUNET_free (head->msg);
622     GNUNET_free (head);
623     head = handle->mq_head;
624   }
625   GNUNET_CLIENT_disconnect (handle->conn);
626   GNUNET_free (handle);
627   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s() END\n", __func__);
628 }
629
630
631 /**
632  * Tries to acquire the given lock(even if the lock has been lost) until the
633  * request is called. If the lock is available the status_cb will be
634  * called. If the lock is busy then the request is queued and status_cb
635  * will be called when the lock has been made available and acquired by us.
636  *
637  * @param handle the handle to the lockmanager service
638  *
639  * @param domain_name name of the locking domain. Clients who want to share
640  *          locks must use the same name for the locking domain. Also the
641  *          domain_name should be selected with the prefix
642  *          "GNUNET_<PROGRAM_NAME>_" to avoid domain name collisions.
643  *
644  *
645  * @param lock which lock to lock
646  *
647  * @param status_cb the callback for signalling when the lock is acquired and
648  *          when it is lost
649  *
650  * @param status_cb_cls the closure to the above callback
651  *
652  * @return the locking request handle for this request
653  */
654 struct GNUNET_LOCKMANAGER_LockingRequest *
655 GNUNET_LOCKMANAGER_acquire_lock (struct GNUNET_LOCKMANAGER_Handle *handle,
656                                  const char *domain_name,
657                                  uint32_t lock,
658                                  GNUNET_LOCKMANAGER_StatusCallback
659                                  status_cb,
660                                  void *status_cb_cls)
661 {
662   struct GNUNET_LOCKMANAGER_LockingRequest *r;
663   struct GNUNET_LOCKMANAGER_Message *msg;
664   struct GNUNET_HashCode hash;
665   size_t domain_name_length;
666   
667   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s()\n", __func__);
668   r = GNUNET_malloc (sizeof (struct GNUNET_LOCKMANAGER_LockingRequest));
669   domain_name_length = strlen (domain_name) + 1;
670   r->handle = handle;
671   r->lock = lock;
672   r->domain = GNUNET_malloc (domain_name_length);
673   r->status = GNUNET_LOCKMANAGER_RELEASE;
674   r->status_cb = status_cb;
675   r->status_cb_cls = status_cb_cls;
676   memcpy (r->domain, domain_name, domain_name_length);
677   msg = generate_acquire_msg (r->domain, r->lock);
678   LOG (GNUNET_ERROR_TYPE_DEBUG, "Queueing ACQUIRE message\n");
679   queue_message (handle, msg);
680   get_key (r->domain, r->lock, &hash);
681   GNUNET_CONTAINER_multihashmap_put (r->handle->hashmap,
682                                      &hash,
683                                      r,
684                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
685   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s() END\n", __func__);
686   return r;
687 }
688
689
690
691 /**
692  * Function to cancel the locking request generated by
693  * GNUNET_LOCKMANAGER_acquire_lock. If the lock is acquired by us then the lock
694  * is released. GNUNET_LOCKMANAGER_StatusCallback will not be called upon any
695  * status changes resulting due to this call.
696  *
697  * @param request the LockingRequest to cancel
698  */
699 void
700 GNUNET_LOCKMANAGER_cancel_request (struct GNUNET_LOCKMANAGER_LockingRequest
701                                    *request)
702 {
703   struct GNUNET_LOCKMANAGER_Message *msg;
704   struct GNUNET_HashCode hash;
705   uint16_t msg_size;
706   size_t domain_name_length;
707
708   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s()\n", __func__);
709   /* FIXME: Stop ACQUIRE retransmissions */
710   if (GNUNET_LOCKMANAGER_SUCCESS == request->status)
711   {
712     domain_name_length = strlen (request->domain) + 1;
713     msg_size = sizeof (struct GNUNET_LOCKMANAGER_Message) 
714       + domain_name_length;
715     msg = GNUNET_malloc (msg_size);
716     msg->header.type = htons (GNUNET_MESSAGE_TYPE_LOCKMANAGER_RELEASE);
717     msg->header.size = htons (msg_size);
718     msg->lock = htonl (request->lock);
719     memcpy (&msg[1], request->domain, domain_name_length);
720     queue_message (request->handle, msg);
721   }
722   get_key (request->domain, request->lock, &hash);
723   GNUNET_assert (GNUNET_YES ==
724                  GNUNET_CONTAINER_multihashmap_remove
725                  (request->handle->hashmap, &hash, request));
726   GNUNET_free (request->domain);
727   GNUNET_free (request);
728   LOG (GNUNET_ERROR_TYPE_DEBUG, "%s() END\n", __func__);
729 }