File Coverage

third_party/modest/source/mycore/thread_queue.c
Criterion Covered Total %
statement 204 328 62.2
branch 82 184 44.5
condition n/a
subroutine n/a
pod n/a
total 286 512 55.8


line stmt bran cond sub pod time code
1             /*
2             Copyright (C) 2015-2017 Alexander Borisov
3            
4             This library is free software; you can redistribute it and/or
5             modify it under the terms of the GNU Lesser General Public
6             License as published by the Free Software Foundation; either
7             version 2.1 of the License, or (at your option) any later version.
8            
9             This library is distributed in the hope that it will be useful,
10             but WITHOUT ANY WARRANTY; without even the implied warranty of
11             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12             Lesser General Public License for more details.
13            
14             You should have received a copy of the GNU Lesser General Public
15             License along with this library; if not, write to the Free Software
16             Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17            
18             Author: lex.borisov@gmail.com (Alexander Borisov)
19             */
20              
21             #include "mycore/thread_queue.h"
22              
23 145           mythread_queue_t * mythread_queue_create(void)
24             {
25 145           return mycore_calloc(1, sizeof(mythread_queue_t));
26             }
27              
28 145           mystatus_t mythread_queue_init(mythread_queue_t* queue, size_t size)
29             {
30 145 50         if(size < 32)
31 0           size = 32;
32            
33 145           queue->nodes_pos_size = 512;
34 145           queue->nodes_size = size;
35 145           queue->nodes = (mythread_queue_node_t**)mycore_calloc(queue->nodes_pos_size, sizeof(mythread_queue_node_t*));
36            
37 145 50         if(queue->nodes == NULL)
38 0           return MyCORE_STATUS_THREAD_ERROR_QUEUE_NODES_MALLOC;
39            
40 145           queue->nodes[queue->nodes_pos] = (mythread_queue_node_t*)mycore_malloc(sizeof(mythread_queue_node_t) * queue->nodes_size);
41            
42 145 50         if(queue->nodes[queue->nodes_pos] == NULL) {
43 0           queue->nodes = mycore_free(queue->nodes);
44 0           return MyCORE_STATUS_THREAD_ERROR_QUEUE_NODE_MALLOC;
45             }
46            
47 145           return MyCORE_STATUS_OK;
48             }
49              
50 145           void mythread_queue_clean(mythread_queue_t* queue)
51             {
52 145           queue->nodes_length = 0;
53 145           queue->nodes_pos = 0;
54 145           queue->nodes_root = 0;
55 145           queue->nodes_uses = 0;
56 145           }
57              
58 144           mythread_queue_t * mythread_queue_destroy(mythread_queue_t* queue)
59             {
60 144 50         if(queue == NULL)
61 0           return NULL;
62            
63 144 50         if(queue->nodes) {
64             /* '<=' it is normal */
65 288 100         for (size_t i = 0; i <= queue->nodes_pos; i++) {
66 144           mycore_free(queue->nodes[i]);
67             }
68            
69 144           mycore_free(queue->nodes);
70             }
71            
72 144           mycore_free(queue);
73            
74 144           return NULL;
75             }
76              
77 145           void mythread_queue_node_clean(mythread_queue_node_t* qnode)
78             {
79 145           memset(qnode, 0, sizeof(mythread_queue_node_t));
80 145           }
81              
82 0           mythread_queue_node_t * mythread_queue_get_prev_node(mythread_queue_node_t* qnode)
83             {
84 0           return qnode->prev;
85             }
86              
87 145           mythread_queue_node_t * mythread_queue_get_current_node(mythread_queue_t* queue)
88             {
89 145           return &queue->nodes[queue->nodes_pos][queue->nodes_length];
90             }
91              
92 0           mythread_queue_node_t * mythread_queue_get_first_node(mythread_queue_t* queue)
93             {
94 0           return &queue->nodes[0][0];
95             }
96              
97 0           size_t mythread_queue_count_used_node(mythread_queue_t* queue)
98             {
99 0           return queue->nodes_uses;
100             }
101              
102 0           mythread_queue_node_t * mythread_queue_node_malloc(mythread_t *mythread, mythread_queue_t* queue, mystatus_t *status)
103             {
104 0           queue->nodes_length++;
105            
106 0 0         if(queue->nodes_length >= queue->nodes_size)
107             {
108 0           queue->nodes_pos++;
109            
110 0 0         if(queue->nodes_pos >= queue->nodes_pos_size)
111             {
112             #ifndef MyCORE_BUILD_WITHOUT_THREADS
113 0 0         if(mythread)
114 0           mythread_queue_list_wait_for_done(mythread, mythread->context);
115             #endif
116 0           size_t new_size = queue->nodes_pos_size + 512;
117 0           mythread_queue_node_t** tmp = mycore_realloc(queue->nodes, sizeof(mythread_queue_node_t*) * new_size);
118            
119 0 0         if(tmp) {
120 0           memset(&tmp[queue->nodes_pos], 0, sizeof(mythread_queue_node_t*) * (new_size - queue->nodes_pos));
121            
122 0           queue->nodes = tmp;
123 0           queue->nodes_pos_size = new_size;
124             }
125             else {
126 0 0         if(status)
127 0           *status = MyCORE_STATUS_THREAD_ERROR_QUEUE_NODES_MALLOC;
128            
129 0           return NULL;
130             }
131             }
132            
133 0 0         if(queue->nodes[queue->nodes_pos] == NULL) {
134 0           queue->nodes[queue->nodes_pos] = (mythread_queue_node_t*)mycore_malloc(sizeof(mythread_queue_node_t) * queue->nodes_size);
135            
136 0 0         if(queue->nodes[queue->nodes_pos] == NULL) {
137 0 0         if(status)
138 0           *status = MyCORE_STATUS_THREAD_ERROR_QUEUE_NODE_MALLOC;
139            
140 0           return NULL;
141             }
142             }
143            
144 0           queue->nodes_length = 0;
145             }
146            
147 0           queue->nodes_uses++;
148            
149 0           return &queue->nodes[queue->nodes_pos][queue->nodes_length];
150             }
151              
152 1663           mythread_queue_node_t * mythread_queue_node_malloc_limit(mythread_t *mythread, mythread_queue_t* queue, size_t limit, mystatus_t *status)
153             {
154 1663           queue->nodes_length++;
155            
156 1663 100         if(queue->nodes_uses >= limit) {
157 339           queue->nodes_uses++;
158            
159             #ifndef MyCORE_BUILD_WITHOUT_THREADS
160 339 50         if(mythread)
161 0           mythread_queue_list_wait_for_done(mythread, mythread->context);
162             #endif
163 339           queue->nodes_length = 0;
164 339           queue->nodes_pos = 0;
165 339           queue->nodes_root = 0;
166 339           queue->nodes_uses = 0;
167             }
168 1324 50         else if(queue->nodes_length >= queue->nodes_size)
169             {
170 0           queue->nodes_pos++;
171            
172 0 0         if(queue->nodes_pos >= queue->nodes_pos_size)
173             {
174             #ifndef MyCORE_BUILD_WITHOUT_THREADS
175 0 0         if(mythread)
176 0           mythread_queue_list_wait_for_done(mythread, mythread->context);
177             #endif
178 0           size_t new_size = queue->nodes_pos_size + 512;
179 0           mythread_queue_node_t** tmp = mycore_realloc(queue->nodes, sizeof(mythread_queue_node_t*) * new_size);
180            
181 0 0         if(tmp) {
182 0           memset(&tmp[queue->nodes_pos], 0, sizeof(mythread_queue_node_t*) * (new_size - queue->nodes_pos));
183            
184 0           queue->nodes = tmp;
185 0           queue->nodes_pos_size = new_size;
186             }
187             else {
188 0 0         if(status)
189 0           *status = MyCORE_STATUS_THREAD_ERROR_QUEUE_NODES_MALLOC;
190            
191 0           return NULL;
192             }
193             }
194            
195 0 0         if(queue->nodes[queue->nodes_pos] == NULL) {
196 0           queue->nodes[queue->nodes_pos] = (mythread_queue_node_t*)mycore_malloc(sizeof(mythread_queue_node_t) * queue->nodes_size);
197            
198 0 0         if(queue->nodes[queue->nodes_pos] == NULL) {
199 0 0         if(status)
200 0           *status = MyCORE_STATUS_THREAD_ERROR_QUEUE_NODE_MALLOC;
201            
202 0           return NULL;
203             }
204             }
205            
206 0           queue->nodes_length = 0;
207             }
208            
209 1663           queue->nodes_uses++;
210            
211 1663           return &queue->nodes[queue->nodes_pos][queue->nodes_length];
212             }
213              
214             #ifndef MyCORE_BUILD_WITHOUT_THREADS
215 22           mythread_queue_node_t * mythread_queue_node_malloc_round(mythread_t *mythread, mythread_queue_list_entry_t *entry)
216             {
217 22           mythread_queue_t* queue = entry->queue;
218            
219 22           queue->nodes_length++;
220            
221 22 50         if(queue->nodes_length >= queue->nodes_size) {
222 0           queue->nodes_uses++;
223            
224             #ifndef MyCORE_BUILD_WITHOUT_THREADS
225 0 0         if(mythread)
226 0           mythread_queue_list_entry_wait_for_done(mythread, entry);
227             #endif
228            
229 0           mythread_queue_list_entry_clean(entry);
230             }
231             else
232 22           queue->nodes_uses++;
233            
234 22           return &queue->nodes[queue->nodes_pos][queue->nodes_length];
235             }
236             #endif
237              
238             #ifndef MyCORE_BUILD_WITHOUT_THREADS
239             /*
240             * Queue List
241             */
242 7           mythread_queue_list_t * mythread_queue_list_create(mystatus_t *status)
243             {
244 7           return (mythread_queue_list_t*)mycore_calloc(1, sizeof(mythread_queue_list_t));
245             }
246              
247 7           void mythread_queue_list_destroy(mythread_queue_list_t* queue_list)
248             {
249 7 50         if(queue_list == NULL)
250 0           return;
251            
252 7           mycore_free(queue_list);
253             }
254              
255 6           size_t mythread_queue_list_get_count(mythread_queue_list_t* queue_list)
256             {
257 6           return queue_list->count;
258             }
259              
260 7           void mythread_queue_list_wait_for_done(mythread_t* mythread, mythread_queue_list_t* queue_list)
261             {
262 7 50         if(queue_list == NULL)
263 0           return;
264            
265 7           mythread_queue_list_entry_t *entry = queue_list->first;
266            
267 7 50         while(entry)
268             {
269 0 0         for (size_t i = 0; i < mythread->entries_length; i++) {
270 0 0         while(entry->thread_param[i].use < entry->queue->nodes_uses)
271 0           mythread_nanosleep_sleep(mythread->timespec);
272             }
273            
274 0           entry = entry->next;
275             }
276             }
277              
278 0           bool mythread_queue_list_see_for_done(mythread_t* mythread, mythread_queue_list_t* queue_list)
279             {
280 0 0         if(queue_list == NULL)
281 0           return true;
282            
283 0           mythread_queue_list_entry_t *entry = queue_list->first;
284            
285 0 0         while(entry)
286             {
287 0 0         for (size_t i = 0; i < mythread->entries_length; i++) {
288 0 0         if(entry->thread_param[i].use < entry->queue->nodes_uses)
289 0           return false;
290             }
291            
292 0           entry = entry->next;
293             }
294            
295 0           return true;
296             }
297              
298 26           bool mythread_queue_list_see_for_done_by_thread(mythread_t* mythread, mythread_queue_list_t* queue_list, mythread_id_t thread_id)
299             {
300 26 50         if(queue_list == NULL)
301 0           return true;
302            
303 26           mythread_queue_list_entry_t *entry = queue_list->first;
304            
305 26 50         while(entry)
306             {
307 0 0         if(entry->thread_param[thread_id].use < entry->queue->nodes_uses)
308 0           return false;
309            
310 0           entry = entry->next;
311             }
312            
313 26           return true;
314             }
315              
316             /*
317             * Queue List Entry
318             */
319 6           mythread_queue_list_entry_t * mythread_queue_list_entry_push(mythread_t** mythread_list, size_t list_size, mythread_queue_list_t* queue_list,
320             mythread_queue_t* queue, size_t thread_param_size, mystatus_t* status)
321             {
322 6 50         if(status)
323 6           *status = MyCORE_STATUS_OK;
324            
325             /* create new entry */
326 6           mythread_queue_list_entry_t* entry = (mythread_queue_list_entry_t*)mycore_calloc(1, sizeof(mythread_queue_list_entry_t));
327 6 50         if(entry == NULL) {
328 0 0         if(status)
329 0           *status = MyCORE_STATUS_THREAD_ERROR_QUEUE_MALLOC;
330            
331 0           return NULL;
332             }
333            
334             /* create thread params */
335 6           entry->thread_param_size = thread_param_size;
336            
337 6 50         if(thread_param_size) {
338 6           entry->thread_param = (mythread_queue_thread_param_t*)mycore_calloc(thread_param_size, sizeof(mythread_queue_thread_param_t));
339            
340 6 50         if(entry->thread_param == NULL) {
341 0           mycore_free(entry);
342            
343 0 0         if(status)
344 0           *status = MyCORE_STATUS_THREAD_ERROR_QUEUE_MALLOC;
345            
346 0           return NULL;
347             }
348             }
349             else
350 0           entry->thread_param = NULL;
351            
352 6           entry->queue = queue;
353            
354 18 100         for(size_t i = 0; i < list_size; i++) {
355 12 50         if(mythread_list[i]) {
356 12 100         if(mythread_list[i]->type == MyTHREAD_TYPE_BATCH)
357 6           mythread_queue_list_entry_make_batch(mythread_list[i], entry);
358             else
359 6           mythread_queue_list_entry_make_stream(mythread_list[i], entry);
360            
361 12           mythread_suspend(mythread_list[i]);
362             }
363             }
364            
365 6 50         if(queue_list->first) {
366 0           queue_list->last->next = entry;
367 0           entry->prev = queue_list->last;
368            
369 0           queue_list->last = entry;
370             }
371             else {
372 6           queue_list->first = entry;
373 6           queue_list->last = entry;
374             }
375            
376 6           queue_list->count++;
377            
378 18 100         for(size_t i = 0; i < list_size; i++)
379 12 50         if(mythread_list[i])
380 12           mythread_resume(mythread_list[i], MyTHREAD_OPT_UNDEF);
381            
382 6           return entry;
383             }
384              
385 6           mythread_queue_list_entry_t * mythread_queue_list_entry_delete(mythread_t** mythread_list, size_t list_size, mythread_queue_list_t *queue_list, mythread_queue_list_entry_t *entry, bool destroy_queue)
386             {
387 18 100         for(size_t i = 0; i < list_size; i++)
388 12 50         if(mythread_list[i])
389 12           mythread_suspend(mythread_list[i]);
390            
391 6           mythread_queue_list_entry_t *next = entry->next;
392 6           mythread_queue_list_entry_t *prev = entry->prev;
393            
394 6 50         if(prev)
395 0           prev->next = next;
396            
397 6 50         if(next)
398 0           next->prev = prev;
399            
400 6 50         if(queue_list->first == entry)
401 6           queue_list->first = next;
402            
403 6 50         if(queue_list->last == entry)
404 6           queue_list->last = prev;
405            
406 6           queue_list->count--;
407            
408 18 100         for(size_t i = 0; i < list_size; i++)
409 12 50         if(mythread_list[i])
410 12           mythread_resume(mythread_list[i], MyTHREAD_OPT_UNDEF);
411            
412 6 50         if(destroy_queue && entry->queue)
    0          
413 0           mythread_queue_destroy(entry->queue);
414            
415 6 50         if(entry->thread_param)
416 6           mycore_free(entry->thread_param);
417            
418 6           mycore_free(entry);
419            
420 6           return NULL;
421             }
422              
423 145           void mythread_queue_list_entry_clean(mythread_queue_list_entry_t *entry)
424             {
425 145 50         if(entry == NULL)
426 145           return;
427            
428 0           mythread_queue_clean(entry->queue);
429             }
430              
431 6           void mythread_queue_list_entry_wait_for_done(mythread_t* mythread, mythread_queue_list_entry_t *entry)
432             {
433 6 50         if(entry == NULL)
434 0           return;
435            
436 18 100         for(size_t i = 0; i < entry->thread_param_size; i++) {
437 20 100         while(entry->thread_param[i].use < entry->queue->nodes_uses)
438 8           mythread_nanosleep_sleep(mythread->timespec);
439             }
440             }
441              
442 0           bool mythread_queue_list_entry_see_for_done(mythread_queue_list_entry_t *entry)
443             {
444 0 0         if(entry == NULL)
445 0           return true;
446            
447 0 0         for(size_t i = 0; i < entry->thread_param_size; i++) {
448 0 0         if(entry->thread_param[i].use < entry->queue->nodes_uses)
449 0           return false;
450             }
451            
452 0           return true;
453             }
454              
455 151           void mythread_queue_list_entry_make_batch(mythread_t* mythread, mythread_queue_list_entry_t* entry)
456             {
457 151 100         if(entry == NULL || mythread == NULL)
    50          
458 145           return;
459            
460 6           size_t i = 0;
461 12 100         for(size_t from = mythread->id_increase; from <= mythread->entries_length; from++) {
462 6           entry->thread_param[from].use = i;
463 6           i++;
464             }
465             }
466              
467 151           void mythread_queue_list_entry_make_stream(mythread_t* mythread, mythread_queue_list_entry_t* entry)
468             {
469 151 100         if(entry == NULL || mythread == NULL)
    50          
470 145           return;
471            
472 18 100         for(size_t from = mythread->id_increase; from <= mythread->entries_length; from++) {
473 12 50         if (from < entry->thread_param_size)
474 12           entry->thread_param[from].use = 0;
475             }
476             }
477              
478             /*
479             * Thread Process Functions
480             */
481 63           bool mythread_function_see_opt(mythread_context_t *ctx, volatile mythread_thread_opt_t opt, mythread_id_t thread_id, size_t done_count, void* timeout)
482             {
483 63           mythread_t *mythread = ctx->mythread;
484 63           mythread_queue_list_t *queue_list = (mythread_queue_list_t*)mythread->context;
485            
486 63 50         if(done_count != queue_list->count)
487 0           return false;
488            
489 63 100         if(opt & MyTHREAD_OPT_STOP)
490             {
491 12 50         if(mythread_queue_list_see_for_done_by_thread(mythread, queue_list, thread_id))
492             {
493 12           ctx->opt = MyTHREAD_OPT_STOP;
494 12           mythread_mutex_wait(mythread, ctx->mutex);
495 12           ctx->opt = MyTHREAD_OPT_UNDEF;
496            
497 12           return false;
498             }
499             }
500 51 100         else if(opt & MyTHREAD_OPT_QUIT)
501             {
502 14 50         if(mythread_queue_list_see_for_done_by_thread(mythread, queue_list, thread_id))
503             {
504 14           mythread_mutex_close(mythread, ctx->mutex);
505 14           mythread_nanosleep_destroy(ctx->timespec);
506            
507 14           ctx->opt = MyTHREAD_OPT_QUIT;
508 14           return true;
509             }
510             }
511            
512 37           mythread_nanosleep_sleep(timeout);
513            
514 37           return false;
515             }
516              
517 7           void * mythread_function_queue_batch(void *arg)
518             {
519 7           mythread_context_t *ctx = (mythread_context_t*)arg;
520 7           mythread_t *mythread = ctx->mythread;
521 7           mythread_queue_list_t *queue_list = (mythread_queue_list_t*)mythread->context;
522 7           mythread_id_t thread_id = myhread_increase_id_by_entry_id(mythread, ctx->id);
523            
524 7           mythread_mutex_wait(mythread, ctx->mutex);
525            
526             do {
527 61 100         if(mythread->opt & MyTHREAD_OPT_WAIT) {
528 6           ctx->opt = MyTHREAD_OPT_WAIT;
529            
530 14 100         while (mythread->opt & MyTHREAD_OPT_WAIT)
531 8           mythread_nanosleep_sleep(ctx->timespec);
532            
533 6           ctx->opt = MyTHREAD_OPT_UNDEF;
534             }
535            
536 61           mythread_queue_list_entry_t *entry = queue_list->first;
537 61           size_t done_count = 0;
538            
539 100 100         while(entry)
540             {
541 39           mythread_queue_thread_param_t *thread_param = &entry->thread_param[ thread_id ];
542            
543 39 100         if(thread_param->use < entry->queue->nodes_uses)
544             {
545 22           size_t pos = thread_param->use / entry->queue->nodes_size;
546 22           size_t len = thread_param->use % entry->queue->nodes_size;
547            
548 22           mythread_queue_node_t *qnode = &entry->queue->nodes[pos][len];
549            
550             //if((qnode->tree->flags & MyCORE_TREE_FLAGS_SINGLE_MODE) == 0)
551 22           ctx->func(ctx->id, (void*)qnode);
552            
553 22           thread_param->use += mythread->entries_length;
554             }
555             else
556 17           done_count++;
557            
558 39           entry = entry->next;
559             }
560            
561 100           if(done_count == queue_list->count &&
562 39           mythread_function_see_opt(ctx, mythread->opt, thread_id, done_count, ctx->timespec))
563             {
564 7           break;
565             }
566             }
567 54           while (1);
568            
569 7           return NULL;
570             }
571              
572 7           void * mythread_function_queue_stream(void *arg)
573             {
574 7           mythread_context_t *ctx = (mythread_context_t*)arg;
575 7           mythread_t * mythread = ctx->mythread;
576 7           mythread_queue_list_t *queue_list = (mythread_queue_list_t*)mythread->context;
577 7           mythread_id_t thread_id = myhread_increase_id_by_entry_id(mythread, ctx->id);
578            
579 7           mythread_mutex_wait(mythread, ctx->mutex);
580            
581             do {
582 46 100         if(mythread->opt & MyTHREAD_OPT_WAIT) {
583 6           ctx->opt = MyTHREAD_OPT_WAIT;
584            
585 20 100         while (mythread->opt & MyTHREAD_OPT_WAIT) {
586 14           mythread_nanosleep_sleep(ctx->timespec);
587             }
588            
589 6           ctx->opt = MyTHREAD_OPT_UNDEF;
590             }
591            
592 46           mythread_queue_list_entry_t *entry = queue_list->first;
593 46           size_t done_count = 0;
594            
595 79 100         while(entry)
596             {
597 33           mythread_queue_thread_param_t *thread_param = &entry->thread_param[ thread_id ];
598            
599 33 100         if(thread_param->use < entry->queue->nodes_uses)
600             {
601 22           size_t pos = thread_param->use / entry->queue->nodes_size;
602 22           size_t len = thread_param->use % entry->queue->nodes_size;
603            
604 22           mythread_queue_node_t *qnode = &entry->queue->nodes[pos][len];
605            
606             //if((qnode->tree->flags & MyCORE_TREE_FLAGS_SINGLE_MODE) == 0)
607 22           ctx->func(ctx->id, (void*)qnode);
608            
609 22           thread_param->use++;
610             }
611             else
612 11           done_count++;
613            
614 33           entry = entry->next;
615             }
616            
617 70           if(done_count == queue_list->count &&
618 24           mythread_function_see_opt(ctx, mythread->opt, thread_id, done_count, ctx->timespec))
619             {
620 7           break;
621             }
622             }
623 39           while(1);
624            
625 7           return NULL;
626             }
627              
628 0           void * mythread_function(void *arg)
629             {
630 0           mythread_context_t *ctx = (mythread_context_t*)arg;
631 0           mythread_t * mythread = ctx->mythread;
632            
633 0           mythread_mutex_wait(mythread, ctx->mutex);
634            
635             do {
636 0           ctx->func(ctx->id, ctx);
637            
638 0           ctx->opt |= MyTHREAD_OPT_DONE;
639            
640 0 0         if(ctx->opt & MyTHREAD_OPT_WAIT) {
641 0 0         while (ctx->opt & MyTHREAD_OPT_WAIT) {
642 0           mythread_nanosleep_sleep(ctx->timespec);
643             }
644             }
645             else {
646 0           ctx->opt |= MyTHREAD_OPT_STOP;
647 0           mythread_mutex_wait(mythread, ctx->mutex);
648             }
649            
650 0 0         if(mythread->opt & MyTHREAD_OPT_QUIT || ctx->opt & MyTHREAD_OPT_QUIT)
    0          
651             {
652 0           mythread_mutex_close(mythread, ctx->mutex);
653 0           mythread_nanosleep_destroy(ctx->timespec);
654            
655 0           ctx->opt = MyTHREAD_OPT_QUIT;
656 0           break;
657             }
658            
659 0           ctx->opt = MyTHREAD_OPT_UNDEF;
660             }
661 0           while(1);
662            
663 0           return NULL;
664             }
665              
666             #endif