varnish-cache/bin/varnishd/cache/cache_session.c
1
/*-
2
 * Copyright (c) 2006 Verdens Gang AS
3
 * Copyright (c) 2006-2011 Varnish Software AS
4
 * All rights reserved.
5
 *
6
 * Author: Poul-Henning Kamp <phk@phk.freebsd.dk>
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions
10
 * are met:
11
 * 1. Redistributions of source code must retain the above copyright
12
 *    notice, this list of conditions and the following disclaimer.
13
 * 2. Redistributions in binary form must reproduce the above copyright
14
 *    notice, this list of conditions and the following disclaimer in the
15
 *    documentation and/or other materials provided with the distribution.
16
 *
17
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20
 * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
21
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27
 * SUCH DAMAGE.
28
 *
29
 * Session management
30
 *
31
 * The overall goal here is to hold as little state as possible for an
32
 * idle session.  This leads to various nasty-ish overloads of struct
33
 * sess fields, for instance ->fd being negative ->reason.
34
 *
35
 */
36
37
#include "config.h"
38
39
#include "cache_varnishd.h"
40
41
#include <errno.h>
42
#include <stdio.h>
43
#include <stdlib.h>
44
45
#include "cache_pool.h"
46
#include "cache_transport.h"
47
48
#include "vsa.h"
49
#include "vtcp.h"
50
#include "vtim.h"
51
#include "waiter/waiter.h"
52
53
/*--------------------------------------------------------------------*/
54
55
void
56 1180
SES_SetTransport(struct worker *wrk, struct sess *sp, struct req *req,
57
    const struct transport *xp)
58
{
59
60 1180
        CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
61 1180
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
62 1180
        CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
63 1180
        CHECK_OBJ_NOTNULL(xp, TRANSPORT_MAGIC);
64 1180
        assert(xp->number > 0);
65
66 1180
        sp->sattr[SA_TRANSPORT] = xp->number;
67 1180
        req->transport = xp;
68 1180
        wrk->task.func = xp->new_session;
69 1180
        wrk->task.priv = req;
70 1180
}
71
72
/*--------------------------------------------------------------------*/
73
74
static int
75 13222
ses_get_attr(const struct sess *sp, enum sess_attr a, void **dst)
76
{
77 13222
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
78 13222
        assert(a < SA_LAST);
79 13222
        AN(dst);
80
81 13222
        if (sp->sattr[a] == 0xffff) {
82 0
                *dst = NULL;
83 0
                return (-1);
84
        } else {
85 13222
                *dst = sp->ws->s + sp->sattr[a];
86 13222
                return (0);
87
        }
88
}
89
90
static int
91 6053
ses_set_attr(const struct sess *sp, enum sess_attr a, const void *src, int sz)
92
{
93
        void *dst;
94 6053
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
95 6053
        assert(a < SA_LAST);
96 6053
        AN(src);
97 6053
        assert(sz > 0);
98
99 6053
        if (sp->sattr[a] == 0xffff)
100 0
                return (-1);
101 6053
        dst = sp->ws->s + sp->sattr[a];
102 6053
        AN(dst);
103 6053
        memcpy(dst, src, sz);
104 6053
        return (0);
105
}
106
107
static void
108 5594
ses_reserve_attr(struct sess *sp, enum sess_attr a, void **dst, int sz)
109
{
110
        ssize_t o;
111
112 5594
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
113 5594
        assert(a < SA_LAST);
114 5594
        assert(sz >= 0);
115 5594
        AN(dst);
116 5594
        o = WS_Reserve(sp->ws, sz);
117 5595
        assert(o >= sz);
118 5595
        *dst = sp->ws->f;
119 5595
        o = sp->ws->f - sp->ws->s;
120 5595
        WS_Release(sp->ws, sz);
121 5595
        assert(o >= 0 && o <= 0xffff);
122 5595
        sp->sattr[a] = (uint16_t)o;
123 5595
}
124
125
#define SESS_ATTR(UP, low, typ, len)                                    \
126
        int                                                             \
127
        SES_Set_##low(const struct sess *sp, const typ *src)            \
128
        {                                                               \
129
                return (ses_set_attr(sp, SA_##UP, src, len));           \
130
        }                                                               \
131
                                                                        \
132
        int                                                             \
133
        SES_Get_##low(const struct sess *sp, typ **dst)                 \
134
        {                                                               \
135
                return (ses_get_attr(sp, SA_##UP, (void**)dst));        \
136
        }                                                               \
137
                                                                        \
138
        void                                                            \
139
        SES_Reserve_##low(struct sess *sp, typ **dst)                   \
140
        {                                                               \
141
                assert(len >= 0);                                       \
142
                ses_reserve_attr(sp, SA_##UP, (void**)dst, len);        \
143
        }
144
145
#include "tbl/sess_attr.h"
146
147
void
148 2234
SES_Set_String_Attr(struct sess *sp, enum sess_attr a, const char *src)
149
{
150
        void *q;
151
152 2234
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
153 2234
        AN(src);
154
155 2234
        switch (a) {
156
#define SESS_ATTR(UP, low, typ, len)    case SA_##UP: assert(len < 0); break;
157
#include "tbl/sess_attr.h"
158 0
        default:  WRONG("wrong sess_attr");
159
        }
160
161 2234
        ses_reserve_attr(sp, a, &q, strlen(src) + 1);
162 2234
        strcpy(q, src);
163 2234
}
164
165
const char *
166 3912
SES_Get_String_Attr(const struct sess *sp, enum sess_attr a)
167
{
168
        void *q;
169
170 3912
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
171
172 3912
        switch (a) {
173
#define SESS_ATTR(UP, low, typ, len)    case SA_##UP: assert(len < 0); break;
174
#include "tbl/sess_attr.h"
175 0
        default:  WRONG("wrong sess_attr");
176
        }
177
178 3912
        if (ses_get_attr(sp, a, &q) < 0)
179 0
                return (NULL);
180 3912
        return (q);
181
}
182
183
/*--------------------------------------------------------------------*/
184
185
void
186 4257
HTC_RxInit(struct http_conn *htc, struct ws *ws)
187
{
188
        ssize_t l;
189
190 4257
        CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);
191 4257
        htc->ws = ws;
192 4257
        (void)WS_Reserve(htc->ws, 0);
193 4257
        htc->rxbuf_b = ws->f;
194 4257
        htc->rxbuf_e = ws->f;
195 4257
        if (htc->pipeline_b != NULL) {
196 47
                AN(htc->pipeline_e);
197
                // assert(WS_Inside(ws, htc->pipeline_b, htc->pipeline_e));
198 47
                l = htc->pipeline_e - htc->pipeline_b;
199 47
                assert(l > 0);
200 47
                assert(l <= ws->r - htc->rxbuf_b);
201 47
                memmove(htc->rxbuf_b, htc->pipeline_b, l);
202 47
                htc->rxbuf_e += l;
203 47
                htc->pipeline_b = NULL;
204 47
                htc->pipeline_e = NULL;
205
        }
206 4257
}
207
208
void
209 3234
HTC_RxPipeline(struct http_conn *htc, void *p)
210
{
211
212 3234
        CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);
213 3234
        if (p == NULL || (char*)p == htc->rxbuf_e) {
214 2439
                htc->pipeline_b = NULL;
215 2439
                htc->pipeline_e = NULL;
216
        } else {
217 795
                assert((char*)p >= htc->rxbuf_b);
218 795
                assert((char*)p < htc->rxbuf_e);
219 795
                htc->pipeline_b = p;
220 795
                htc->pipeline_e = htc->rxbuf_e;
221
        }
222 3234
}
223
224
/*----------------------------------------------------------------------
225
 * Receive a request/packet/whatever, with timeouts
226
 *
227
 * t0 is when we start
228
 * *t1 becomes time of first non-idle rx
229
 * *t2 becomes time of complete rx
230
 * ti is when we return IDLE if nothing has arrived
231
 * tn is when we timeout on non-complete
232
 */
233
234
enum htc_status_e
235 4257
HTC_RxStuff(struct http_conn *htc, htc_complete_f *func,
236
    double *t1, double *t2, double ti, double tn, int maxbytes)
237
{
238
        double tmo;
239
        double now;
240
        enum htc_status_e hs;
241
        ssize_t z;
242
243 4257
        CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);
244 4257
        AN(htc->rfd);
245 4257
        assert(*htc->rfd > 0);
246 4257
        AN(htc->ws->r);
247 4257
        AN(htc->rxbuf_b);
248 4257
        assert(htc->rxbuf_b <= htc->rxbuf_e);
249
250 4257
        AZ(isnan(tn));
251 4256
        if (t1 != NULL)
252 2662
                assert(isnan(*t1));
253
254 4256
        if (htc->rxbuf_e == htc->ws->r) {
255
                /* Can't work with a zero size buffer */
256 1
                WS_ReleaseP(htc->ws, htc->rxbuf_b);
257 1
                return (HTC_S_OVERFLOW);
258
        }
259 4255
        z = (htc->ws->r - htc->rxbuf_b);
260 4255
        if (z < maxbytes)
261 44
                maxbytes = z;
262
263
        while (1) {
264 7898
                now = VTIM_real();
265 7899
                AZ(htc->pipeline_b);
266 7899
                AZ(htc->pipeline_e);
267 7899
                assert(htc->rxbuf_e <= htc->ws->r);
268
269 7899
                hs = func(htc);
270 7899
                if (hs == HTC_S_OVERFLOW || hs == HTC_S_JUNK) {
271 7
                        WS_ReleaseP(htc->ws, htc->rxbuf_b);
272 7
                        return (hs);
273
                }
274 7892
                if (hs == HTC_S_COMPLETE) {
275 3270
                        WS_ReleaseP(htc->ws, htc->rxbuf_e);
276
                        /* Got it, run with it */
277 3270
                        if (t1 != NULL && isnan(*t1))
278 1659
                                *t1 = now;
279 3270
                        if (t2 != NULL)
280 1722
                                *t2 = now;
281 3270
                        return (HTC_S_COMPLETE);
282
                }
283 4622
                if (hs == HTC_S_MORE) {
284
                        /* Working on it */
285 555
                        if (t1 != NULL && isnan(*t1))
286 65
                                *t1 = now;
287 4067
                } else if (hs == HTC_S_EMPTY)
288 4067
                        htc->rxbuf_e = htc->rxbuf_b;
289
                else
290 0
                        WRONG("htc_status_e");
291
292 4622
                tmo = tn - now;
293 4622
                if (!isnan(ti) && ti < tn && hs == HTC_S_EMPTY)
294 4064
                        tmo = ti - now;
295 4622
                z = maxbytes - (htc->rxbuf_e - htc->rxbuf_b);
296 4622
                assert(z >= 0);
297 4622
                if (z == 0) {
298 3
                        WS_ReleaseP(htc->ws, htc->rxbuf_b);
299 3
                        return (HTC_S_OVERFLOW);
300
                }
301 4619
                if (tmo <= 0.0)
302 209
                        tmo = 1e-3;
303 4619
                z = VTCP_read(*htc->rfd, htc->rxbuf_e, z, tmo);
304 4618
                if (z == 0 || z == -1) {
305 860
                        WS_ReleaseP(htc->ws, htc->rxbuf_b);
306 860
                        return (HTC_S_EOF);
307 3758
                } else if (z > 0)
308 3510
                        htc->rxbuf_e += z;
309 248
                else if (z == -2) {
310 248
                        if (hs == HTC_S_EMPTY && ti <= now) {
311 114
                                WS_ReleaseP(htc->ws, htc->rxbuf_b);
312 114
                                return (HTC_S_IDLE);
313
                        }
314 134
                        if (tn <= now) {
315 1
                                WS_ReleaseP(htc->ws, htc->rxbuf_b);
316 1
                                return (HTC_S_TIMEOUT);
317
                        }
318
                }
319 3643
        }
320
}
321
322
/*--------------------------------------------------------------------
323
 * Get a new session, preferably by recycling an already ready one
324
 *
325
 * Layout is:
326
 *      struct sess
327
 *      workspace
328
 */
329
330
struct sess *
331 1093
SES_New(struct pool *pp)
332
{
333
        struct sess *sp;
334
        unsigned sz;
335
        char *p, *e;
336
337 1093
        CHECK_OBJ_NOTNULL(pp, POOL_MAGIC);
338 1093
        sp = MPL_Get(pp->mpl_sess, &sz);
339 1093
        sp->magic = SESS_MAGIC;
340 1093
        sp->pool = pp;
341 1093
        sp->refcnt = 1;
342 1093
        memset(sp->sattr, 0xff, sizeof sp->sattr);
343
344 1093
        e = (char*)sp + sz;
345 1093
        p = (char*)(sp + 1);
346 1093
        p = (void*)PRNDUP(p);
347 1093
        assert(p < e);
348 1093
        WS_Init(sp->ws, "ses", p, e - p);
349
350 1093
        sp->t_open = NAN;
351 1093
        sp->t_idle = NAN;
352 1093
        Lck_New(&sp->mtx, lck_sess);
353 1093
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
354 1093
        return (sp);
355
}
356
357
/*--------------------------------------------------------------------
358
 * Reschedule a request on a work-thread from its sessions pool
359
 *
360
 * This is used to reschedule requests waiting on busy objects
361
 */
362
363
int
364 20
SES_Reschedule_Req(struct req *req, enum task_prio prio)
365
{
366
        struct sess *sp;
367
        struct pool *pp;
368
369 20
        CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
370 20
        sp = req->sp;
371 20
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
372 20
        pp = sp->pool;
373 20
        CHECK_OBJ_NOTNULL(pp, POOL_MAGIC);
374 20
        AN(TASK_QUEUE_CLIENT(prio));
375
376 20
        AN(req->task.func);
377
378 20
        return (Pool_Task(pp, &req->task, prio));
379
}
380
381
/*--------------------------------------------------------------------
382
 * Handle a session (from waiter)
383
 */
384
385
static void v_matchproto_(waiter_handle_f)
386 110
ses_handle(struct waited *wp, enum wait_event ev, double now)
387
{
388
        struct sess *sp;
389
        struct pool *pp;
390
        struct pool_task *tp;
391
        const struct transport *xp;
392
393 110
        CHECK_OBJ_NOTNULL(wp, WAITED_MAGIC);
394 110
        CAST_OBJ_NOTNULL(sp, wp->priv1, SESS_MAGIC);
395 110
        CAST_OBJ_NOTNULL(xp, (const void*)wp->priv2, TRANSPORT_MAGIC);
396 110
        AN(wp->priv2);
397 110
        assert((void *)sp->ws->f == wp);
398 110
        wp->magic = 0;
399 110
        wp = NULL;
400
401 110
        WS_Release(sp->ws, 0);
402
403 110
        switch (ev) {
404
        case WAITER_TIMEOUT:
405 4
                SES_Delete(sp, SC_RX_TIMEOUT, now);
406 4
                break;
407
        case WAITER_REMCLOSE:
408 12
                SES_Delete(sp, SC_REM_CLOSE, now);
409 12
                break;
410
        case WAITER_ACTION:
411 94
                pp = sp->pool;
412 94
                CHECK_OBJ_NOTNULL(pp, POOL_MAGIC);
413 94
                assert(sizeof *tp <= WS_Reserve(sp->ws, sizeof *tp));
414 94
                tp = (void*)sp->ws->f;
415 94
                tp->func = xp->unwait;
416 94
                tp->priv = sp;
417 94
                if (Pool_Task(pp, tp, TASK_QUEUE_REQ))
418 0
                        SES_Delete(sp, SC_OVERLOAD, now);
419 94
                break;
420
        case WAITER_CLOSE:
421 0
                WRONG("Should not see WAITER_CLOSE on client side");
422
                break;
423
        default:
424 0
                WRONG("Wrong event in ses_handle");
425
        }
426 110
}
427
428
/*--------------------------------------------------------------------
429
 */
430
431
void
432 110
SES_Wait(struct sess *sp, const struct transport *xp)
433
{
434
        struct pool *pp;
435
        struct waited *wp;
436
437 110
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
438 110
        CHECK_OBJ_NOTNULL(xp, TRANSPORT_MAGIC);
439 110
        pp = sp->pool;
440 110
        CHECK_OBJ_NOTNULL(pp, POOL_MAGIC);
441 110
        assert(sp->fd > 0);
442
        /*
443
         * XXX: waiter_epoll prevents us from zeroing the struct because
444
         * XXX: it keeps state across calls.
445
         */
446 110
        if (VTCP_nonblocking(sp->fd)) {
447 0
                SES_Delete(sp, SC_REM_CLOSE, NAN);
448 0
                return;
449
        }
450
451
        /*
452
         * put struct waited on the workspace
453
         */
454 110
        if (WS_Reserve(sp->ws, sizeof(struct waited))
455
            < sizeof(struct waited)) {
456 0
                SES_Delete(sp, SC_OVERLOAD, NAN);
457 0
                return;
458
        }
459 110
        wp = (void*)sp->ws->f;
460 110
        INIT_OBJ(wp, WAITED_MAGIC);
461 110
        wp->fd = sp->fd;
462 110
        wp->priv1 = sp;
463 110
        wp->priv2 = (uintptr_t)xp;
464 110
        wp->idle = sp->t_idle;
465 110
        wp->func = ses_handle;
466 110
        wp->tmo = &cache_param->timeout_idle;
467 110
        if (Wait_Enter(pp->waiter, wp))
468 0
                SES_Delete(sp, SC_PIPE_OVERFLOW, NAN);
469
}
470
471
/*--------------------------------------------------------------------
472
 * Update sc_ counters by reason
473
 *
474
 * assuming that the approximation of non-atomic global counters is sufficient.
475
 * if not: update to per-wrk
476
 */
477
478
static void
479 1088
ses_close_acct(enum sess_close reason)
480
{
481 1088
        int i = 0;
482
483 1088
        assert(reason != SC_NULL);
484 1088
        switch (reason) {
485
#define SESS_CLOSE(reason, stat, err, desc)             \
486
        case SC_ ## reason:                             \
487
                VSC_C_main->sc_ ## stat++;              \
488
                i = err;                                \
489
                break;
490
#include "tbl/sess_close.h"
491
492
        default:
493 0
                WRONG("Wrong event in ses_close_acct");
494
        }
495 1088
        if (i)
496 198
                VSC_C_main->sess_closed_err++;
497 1088
}
498
499
/*--------------------------------------------------------------------
500
 * Close a session's connection.
501
 * XXX: Technically speaking we should catch a t_end timestamp here
502
 * XXX: for SES_Delete() to use.
503
 */
504
505
void
506 1089
SES_Close(struct sess *sp, enum sess_close reason)
507
{
508
        int i;
509
510 1089
        assert(reason > 0);
511 1089
        assert(sp->fd > 0);
512 1089
        i = close(sp->fd);
513 1089
        assert(i == 0 || errno != EBADF); /* XXX EINVAL seen */
514 1089
        sp->fd = -(int)reason;
515 1089
        ses_close_acct(reason);
516 1088
}
517
518
/*--------------------------------------------------------------------
519
 * Report and dismantle a session.
520
 */
521
522
void
523 1089
SES_Delete(struct sess *sp, enum sess_close reason, double now)
524
{
525
526 1089
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
527
528 1089
        if (reason != SC_NULL)
529 922
                SES_Close(sp, reason);
530 1088
        assert(sp->fd < 0);
531
532 1088
        if (isnan(now))
533 1072
                now = VTIM_real();
534 1089
        AZ(isnan(sp->t_open));
535 1089
        if (now < sp->t_open) {
536 0
                VSL(SLT_Debug, sp->vxid,
537
                    "Clock step (now=%f < t_open=%f)",
538
                    now, sp->t_open);
539 0
                if (now + cache_param->clock_step < sp->t_open)
540 0
                        WRONG("Clock step detected");
541 0
                now = sp->t_open; /* Do not log negatives */
542
        }
543
544 1089
        if (reason == SC_NULL)
545 167
                reason = (enum sess_close)-sp->fd;
546
547 1089
        VSL(SLT_SessClose, sp->vxid, "%s %.3f",
548 1089
            sess_close_2str(reason, 0), now - sp->t_open);
549 1089
        VSL(SLT_End, sp->vxid, "%s", "");
550 1089
        SES_Rel(sp);
551 1089
}
552
553
/*--------------------------------------------------------------------
554
 */
555
556
void
557 1574
SES_Ref(struct sess *sp)
558
{
559
560 1574
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
561 1574
        Lck_Lock(&sp->mtx);
562 1574
        assert(sp->refcnt > 0);
563 1574
        sp->refcnt++;
564 1574
        Lck_Unlock(&sp->mtx);
565 1574
}
566
567
void
568 2662
SES_Rel(struct sess *sp)
569
{
570
        int i;
571
        struct pool *pp;
572
573 2662
        CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
574 2662
        pp = sp->pool;
575 2662
        CHECK_OBJ_NOTNULL(pp, POOL_MAGIC);
576
577 2662
        Lck_Lock(&sp->mtx);
578 2662
        assert(sp->refcnt > 0);
579 2662
        i = --sp->refcnt;
580 2662
        Lck_Unlock(&sp->mtx);
581 2662
        if (i)
582 4235
                return;
583 1089
        Lck_Delete(&sp->mtx);
584 1089
        MPL_Free(sp->pool->mpl_sess, sp);
585
}
586
587
/*--------------------------------------------------------------------
588
 * Create and delete pools
589
 */
590
591
void
592 1221
SES_NewPool(struct pool *pp, unsigned pool_no)
593
{
594
        char nb[8];
595
596 1221
        CHECK_OBJ_NOTNULL(pp, POOL_MAGIC);
597 1221
        bprintf(nb, "req%u", pool_no);
598 1221
        pp->mpl_req = MPL_New(nb, &cache_param->req_pool,
599 1221
            &cache_param->workspace_client);
600 1221
        bprintf(nb, "sess%u", pool_no);
601 1221
        pp->mpl_sess = MPL_New(nb, &cache_param->sess_pool,
602 1221
            &cache_param->workspace_session);
603
604 1221
        pp->waiter = Waiter_New();
605 1221
}
606
607
void
608 2
SES_DestroyPool(struct pool *pp)
609
{
610 2
        MPL_Destroy(&pp->mpl_req);
611 2
        MPL_Destroy(&pp->mpl_sess);
612 2
        Waiter_Destroy(&pp->waiter);
613 2
}