1 | #include "EXTERN.h" |
1 | #include "EXTERN.h" |
2 | #include "perl.h" |
2 | #include "perl.h" |
3 | #include "XSUB.h" |
3 | #include "XSUB.h" |
4 | |
4 | |
|
|
5 | #include "ecb.h" |
5 | #include "schmorp.h" |
6 | #include "schmorp.h" |
6 | |
7 | |
7 | typedef volatile sig_atomic_t atomic_t; |
8 | typedef volatile sig_atomic_t atomic_t; |
8 | |
9 | |
9 | static int *sig_pending, *psig_pend; /* make local copies because of missing THX */ |
10 | static int *sig_pending, *psig_pend; /* make local copies because of missing THX */ |
… | |
… | |
31 | void (*c_cb)(pTHX_ void *c_arg, int value); |
32 | void (*c_cb)(pTHX_ void *c_arg, int value); |
32 | void *c_arg; |
33 | void *c_arg; |
33 | SV *fh_r, *fh_w; |
34 | SV *fh_r, *fh_w; |
34 | SV *value; |
35 | SV *value; |
35 | int signum; |
36 | int signum; |
|
|
37 | int autodrain; |
|
|
38 | ANY *scope_savestack; |
36 | volatile int blocked; |
39 | volatile int blocked; |
37 | |
40 | |
38 | s_epipe ep; |
41 | s_epipe ep; |
39 | int fd_wlen; |
42 | int fd_wlen; |
40 | atomic_t fd_enable; |
43 | atomic_t fd_enable; |
41 | atomic_t pending; |
44 | atomic_t pending; |
42 | volatile IV *valuep; |
45 | volatile IV *valuep; |
|
|
46 | atomic_t hysteresis; |
43 | } async_t; |
47 | } async_t; |
44 | |
48 | |
45 | static AV *asyncs; |
49 | static AV *asyncs; |
46 | static async_t *sig_async [SIG_SIZE]; |
50 | static async_t *sig_async [SIG_SIZE]; |
47 | |
51 | |
48 | #define SvASYNC_nrv(sv) INT2PTR (async_t *, SvIVX (sv)) |
52 | #define SvASYNC_nrv(sv) INT2PTR (async_t *, SvIVX (sv)) |
49 | #define SvASYNC(rv) SvASYNC_nrv (SvRV (rv)) |
53 | #define SvASYNC(rv) SvASYNC_nrv (SvRV (rv)) |
50 | |
54 | |
|
|
55 | static void async_signal (void *signal_arg, int value); |
|
|
56 | |
|
|
57 | static void |
|
|
58 | setsig (int signum, void (*handler)(int)) |
|
|
59 | { |
|
|
60 | #if _WIN32 |
|
|
61 | signal (signum, handler); |
|
|
62 | #else |
|
|
63 | struct sigaction sa; |
|
|
64 | sa.sa_handler = handler; |
|
|
65 | sigfillset (&sa.sa_mask); |
|
|
66 | sa.sa_flags = 0; /* if we interrupt a syscall, we might drain the pipe before it became ready */ |
|
|
67 | sigaction (signum, &sa, 0); |
|
|
68 | #endif |
|
|
69 | } |
|
|
70 | |
|
|
71 | static void |
|
|
72 | async_sigsend (int signum) |
|
|
73 | { |
|
|
74 | async_signal (sig_async [signum], 0); |
|
|
75 | } |
|
|
76 | |
51 | /* the main workhorse to signal */ |
77 | /* the main workhorse to signal */ |
52 | static void |
78 | static void |
53 | async_signal (void *signal_arg, int value) |
79 | async_signal (void *signal_arg, int value) |
54 | { |
80 | { |
55 | static char pipedata [8]; |
81 | static char pipedata [8]; |
56 | |
82 | |
57 | async_t *async = (async_t *)signal_arg; |
83 | async_t *async = (async_t *)signal_arg; |
58 | int pending = async->pending; |
84 | int pending = async->pending; |
59 | |
85 | |
|
|
86 | if (async->hysteresis) |
|
|
87 | setsig (async->signum, SIG_IGN); |
|
|
88 | |
60 | *async->valuep = value ? value : 1; |
89 | *async->valuep = value ? value : 1; |
|
|
90 | ECB_MEMORY_FENCE_RELEASE; |
61 | async->pending = 1; |
91 | async->pending = 1; |
|
|
92 | ECB_MEMORY_FENCE_RELEASE; |
62 | async_pending = 1; |
93 | async_pending = 1; |
|
|
94 | ECB_MEMORY_FENCE_RELEASE; |
|
|
95 | |
|
|
96 | if (!async->blocked) |
|
|
97 | { |
63 | psig_pend [9] = 1; |
98 | psig_pend [9] = 1; |
|
|
99 | ECB_MEMORY_FENCE_RELEASE; |
64 | *sig_pending = 1; |
100 | *sig_pending = 1; |
|
|
101 | ECB_MEMORY_FENCE_RELEASE; |
|
|
102 | } |
65 | |
103 | |
66 | if (!pending && async->fd_enable && async->ep.len) |
104 | if (!pending && async->fd_enable && async->ep.len) |
67 | s_epipe_signal (&async->ep); |
105 | s_epipe_signal (&async->ep); |
68 | } |
106 | } |
69 | |
107 | |
… | |
… | |
74 | int value = *async->valuep; |
112 | int value = *async->valuep; |
75 | |
113 | |
76 | *async->valuep = 0; |
114 | *async->valuep = 0; |
77 | async->pending = 0; |
115 | async->pending = 0; |
78 | |
116 | |
|
|
117 | /* restore signal */ |
|
|
118 | if (async->hysteresis) |
|
|
119 | setsig (async->signum, async_sigsend); |
|
|
120 | |
79 | /* drain pipe */ |
121 | /* drain pipe */ |
80 | if (async->fd_enable && async->ep.len) |
122 | if (async->fd_enable && async->ep.len && async->autodrain) |
81 | s_epipe_drain (&async->ep); |
123 | s_epipe_drain (&async->ep); |
82 | |
124 | |
83 | if (async->c_cb) |
125 | if (async->c_cb) |
84 | { |
126 | { |
85 | dTHX; |
127 | dTHX; |
… | |
… | |
131 | static void |
173 | static void |
132 | handle_asyncs (void) |
174 | handle_asyncs (void) |
133 | { |
175 | { |
134 | int i; |
176 | int i; |
135 | |
177 | |
|
|
178 | ECB_MEMORY_FENCE_ACQUIRE; |
|
|
179 | |
136 | async_pending = 0; |
180 | async_pending = 0; |
137 | |
181 | |
138 | for (i = AvFILLp (asyncs); i >= 0; --i) |
182 | for (i = AvFILLp (asyncs); i >= 0; --i) |
139 | { |
183 | { |
|
|
184 | SV *async_sv = AvARRAY (asyncs)[i]; |
140 | async_t *async = SvASYNC_nrv (AvARRAY (asyncs)[i]); |
185 | async_t *async = SvASYNC_nrv (async_sv); |
141 | |
186 | |
142 | if (async->pending && !async->blocked) |
187 | if (async->pending && !async->blocked) |
|
|
188 | { |
|
|
189 | /* temporarily keep a refcount */ |
|
|
190 | SvREFCNT_inc (async_sv); |
143 | handle_async (async); |
191 | handle_async (async); |
|
|
192 | SvREFCNT_dec (async_sv); |
|
|
193 | |
|
|
194 | /* the handler could have deleted any number of asyncs */ |
|
|
195 | if (i > AvFILLp (asyncs)) |
|
|
196 | i = AvFILLp (asyncs); |
|
|
197 | } |
144 | } |
198 | } |
145 | } |
199 | } |
146 | |
200 | |
147 | #if HAS_SA_SIGINFO |
201 | #if HAS_SA_SIGINFO |
148 | static Signal_t async_sighandler (int signum, siginfo_t *si, void *sarg) |
202 | static Signal_t async_sighandler (int signum, siginfo_t *si, void *sarg) |
… | |
… | |
160 | else |
214 | else |
161 | old_sighandler (signum); |
215 | old_sighandler (signum); |
162 | } |
216 | } |
163 | #endif |
217 | #endif |
164 | |
218 | |
165 | static void |
|
|
166 | async_sigsend (int signum) |
|
|
167 | { |
|
|
168 | async_signal (sig_async [signum], 0); |
|
|
169 | } |
|
|
170 | |
|
|
171 | #define block(async) ++(async)->blocked |
219 | #define block(async) ++(async)->blocked |
172 | |
220 | |
173 | static void |
221 | static void |
174 | unblock (async_t *async) |
222 | unblock (async_t *async) |
175 | { |
223 | { |
… | |
… | |
180 | |
228 | |
181 | static void |
229 | static void |
182 | scope_block_cb (pTHX_ void *async_sv) |
230 | scope_block_cb (pTHX_ void *async_sv) |
183 | { |
231 | { |
184 | async_t *async = SvASYNC_nrv ((SV *)async_sv); |
232 | async_t *async = SvASYNC_nrv ((SV *)async_sv); |
|
|
233 | |
|
|
234 | async->scope_savestack = 0; |
185 | unblock (async); |
235 | unblock (async); |
186 | SvREFCNT_dec (async_sv); |
236 | SvREFCNT_dec (async_sv); |
|
|
237 | } |
|
|
238 | |
|
|
239 | static void |
|
|
240 | scope_block (SV *async_sv) |
|
|
241 | { |
|
|
242 | async_t *async = SvASYNC_nrv (async_sv); |
|
|
243 | |
|
|
244 | /* as a heuristic, we skip the scope block if we already are blocked */ |
|
|
245 | /* and the existing scope block used the same savestack */ |
|
|
246 | |
|
|
247 | if (!async->scope_savestack || async->scope_savestack != PL_savestack) |
|
|
248 | { |
|
|
249 | async->scope_savestack = PL_savestack; |
|
|
250 | block (async); |
|
|
251 | |
|
|
252 | LEAVE; /* unfortunately, perl sandwiches XS calls into ENTER/LEAVE */ |
|
|
253 | SAVEDESTRUCTOR_X (scope_block_cb, (void *)SvREFCNT_inc (async_sv)); |
|
|
254 | ENTER; /* unfortunately, perl sandwiches XS calls into ENTER/LEAVE */ |
|
|
255 | } |
187 | } |
256 | } |
188 | |
257 | |
189 | MODULE = Async::Interrupt PACKAGE = Async::Interrupt |
258 | MODULE = Async::Interrupt PACKAGE = Async::Interrupt |
190 | |
259 | |
191 | BOOT: |
260 | BOOT: |
… | |
… | |
201 | void |
270 | void |
202 | _alloc (SV *cb, void *c_cb, void *c_arg, SV *fh_r, SV *fh_w, SV *signl, SV *pvalue) |
271 | _alloc (SV *cb, void *c_cb, void *c_arg, SV *fh_r, SV *fh_w, SV *signl, SV *pvalue) |
203 | PPCODE: |
272 | PPCODE: |
204 | { |
273 | { |
205 | SV *cv = SvOK (cb) ? SvREFCNT_inc (s_get_cv_croak (cb)) : 0; |
274 | SV *cv = SvOK (cb) ? SvREFCNT_inc (s_get_cv_croak (cb)) : 0; |
206 | async_t *async; |
275 | async_t *async; |
207 | |
276 | |
208 | Newz (0, async, 1, async_t); |
277 | Newz (0, async, 1, async_t); |
209 | |
278 | |
210 | XPUSHs (sv_2mortal (newSViv (PTR2IV (async)))); |
279 | XPUSHs (sv_2mortal (newSViv (PTR2IV (async)))); |
211 | /* TODO: need to bless right now to ensure deallocation */ |
280 | /* TODO: need to bless right now to ensure deallocation */ |
… | |
… | |
233 | SvIOK_only (async->value); /* just to be sure */ |
302 | SvIOK_only (async->value); /* just to be sure */ |
234 | SvREADONLY_on (async->value); |
303 | SvREADONLY_on (async->value); |
235 | |
304 | |
236 | async->valuep = &(SvIVX (async->value)); |
305 | async->valuep = &(SvIVX (async->value)); |
237 | |
306 | |
|
|
307 | async->autodrain = 1; |
238 | async->cb = cv; |
308 | async->cb = cv; |
239 | async->c_cb = c_cb; |
309 | async->c_cb = c_cb; |
240 | async->c_arg = c_arg; |
310 | async->c_arg = c_arg; |
241 | async->signum = SvOK (signl) ? s_signum_croak (signl) : 0; |
311 | async->signum = SvOK (signl) ? s_signum_croak (signl) : 0; |
242 | |
312 | |
… | |
… | |
244 | { |
314 | { |
245 | if (async->signum < 0) |
315 | if (async->signum < 0) |
246 | croak ("Async::Interrupt::new got passed illegal signal name or number: %s", SvPV_nolen (signl)); |
316 | croak ("Async::Interrupt::new got passed illegal signal name or number: %s", SvPV_nolen (signl)); |
247 | |
317 | |
248 | sig_async [async->signum] = async; |
318 | sig_async [async->signum] = async; |
249 | #if _WIN32 |
|
|
250 | signal (async->signum, async_sigsend); |
319 | setsig (async->signum, async_sigsend); |
251 | #else |
|
|
252 | { |
|
|
253 | struct sigaction sa = { }; |
|
|
254 | sa.sa_handler = async_sigsend; |
|
|
255 | sigfillset (&sa.sa_mask); |
|
|
256 | sigaction (async->signum, &sa, 0); |
|
|
257 | } |
|
|
258 | #endif |
|
|
259 | } |
320 | } |
260 | } |
321 | } |
|
|
322 | |
|
|
323 | void |
|
|
324 | signal_hysteresis (async_t *async, int enable) |
|
|
325 | CODE: |
|
|
326 | async->hysteresis = enable; |
261 | |
327 | |
262 | void |
328 | void |
263 | signal_func (async_t *async) |
329 | signal_func (async_t *async) |
264 | PPCODE: |
330 | PPCODE: |
265 | EXTEND (SP, 2); |
331 | EXTEND (SP, 2); |
266 | PUSHs (sv_2mortal (newSViv (PTR2IV (async_signal)))); |
332 | PUSHs (sv_2mortal (newSViv (PTR2IV (async_signal)))); |
267 | PUSHs (sv_2mortal (newSViv (PTR2IV (async)))); |
333 | PUSHs (sv_2mortal (newSViv (PTR2IV (async)))); |
268 | |
334 | |
|
|
335 | void |
|
|
336 | scope_block_func (SV *self) |
|
|
337 | PPCODE: |
|
|
338 | EXTEND (SP, 2); |
|
|
339 | PUSHs (sv_2mortal (newSViv (PTR2IV (scope_block)))); |
|
|
340 | PUSHs (sv_2mortal (newSViv (PTR2IV (SvRV (self))))); |
|
|
341 | |
269 | IV |
342 | IV |
270 | c_var (async_t *async) |
343 | c_var (async_t *async) |
271 | CODE: |
344 | CODE: |
272 | RETVAL = PTR2IV (async->valuep); |
345 | RETVAL = PTR2IV (async->valuep); |
273 | OUTPUT: |
346 | OUTPUT: |
274 | RETVAL |
347 | RETVAL |
275 | |
348 | |
276 | void |
349 | void |
|
|
350 | handle (async_t *async) |
|
|
351 | CODE: |
|
|
352 | handle_async (async); |
|
|
353 | |
|
|
354 | void |
277 | signal (async_t *async, int value = 1) |
355 | signal (async_t *async, int value = 1) |
278 | CODE: |
356 | CODE: |
279 | async_signal (async, value); |
357 | async_signal (async, value); |
280 | |
358 | |
281 | void |
359 | void |
… | |
… | |
289 | unblock (async); |
367 | unblock (async); |
290 | |
368 | |
291 | void |
369 | void |
292 | scope_block (SV *self) |
370 | scope_block (SV *self) |
293 | CODE: |
371 | CODE: |
294 | { |
372 | scope_block (SvRV (self)); |
295 | SV *async_sv = SvRV (self); |
|
|
296 | async_t *async = SvASYNC_nrv (async_sv); |
|
|
297 | block (async); |
|
|
298 | |
|
|
299 | LEAVE; /* unfortunately, perl sandwiches XS calls into ENTER/LEAVE */ |
|
|
300 | SAVEDESTRUCTOR_X (scope_block_cb, (void *)SvREFCNT_inc (async_sv)); |
|
|
301 | ENTER; /* unfortunately, perl sandwiches XS calls into ENTER/LEAVE */ |
|
|
302 | } |
|
|
303 | |
373 | |
304 | void |
374 | void |
305 | pipe_enable (async_t *async) |
375 | pipe_enable (async_t *async) |
306 | ALIAS: |
376 | ALIAS: |
307 | pipe_enable = 1 |
377 | pipe_enable = 1 |
308 | pipe_disable = 0 |
378 | pipe_disable = 0 |
309 | CODE: |
379 | CODE: |
310 | async->fd_enable = ix; |
380 | async->fd_enable = ix; |
311 | |
381 | |
312 | int |
382 | int |
… | |
… | |
327 | |
397 | |
328 | RETVAL = async->ep.fd [0]; |
398 | RETVAL = async->ep.fd [0]; |
329 | OUTPUT: |
399 | OUTPUT: |
330 | RETVAL |
400 | RETVAL |
331 | |
401 | |
|
|
402 | int |
|
|
403 | pipe_autodrain (async_t *async, int enable = -1) |
|
|
404 | CODE: |
|
|
405 | RETVAL = async->autodrain; |
|
|
406 | if (enable >= 0) |
|
|
407 | async->autodrain = enable; |
|
|
408 | OUTPUT: |
|
|
409 | RETVAL |
|
|
410 | |
|
|
411 | void |
|
|
412 | pipe_drain (async_t *async) |
|
|
413 | CODE: |
|
|
414 | if (async->ep.len) |
|
|
415 | s_epipe_drain (&async->ep); |
332 | |
416 | |
333 | void |
417 | void |
334 | post_fork (async_t *async) |
418 | post_fork (async_t *async) |
335 | CODE: |
419 | CODE: |
336 | if (async->ep.len) |
420 | if (async->ep.len) |
337 | { |
421 | { |
338 | int res; |
422 | int res; |
339 | |
423 | |
340 | /*block (async);*//*TODO*/ |
424 | /*block (async);*//*TODO*/ |
341 | res = s_epipe_renew (&async->ep); |
425 | res = s_epipe_renew (&async->ep); |
342 | /*unblock (async);*//*TODO*/ |
426 | /*unblock (async);*//*TODO*/ |
343 | |
427 | |
… | |
… | |
347 | |
431 | |
348 | void |
432 | void |
349 | DESTROY (SV *self) |
433 | DESTROY (SV *self) |
350 | CODE: |
434 | CODE: |
351 | { |
435 | { |
352 | int i; |
436 | int i; |
353 | SV *async_sv = SvRV (self); |
437 | SV *async_sv = SvRV (self); |
354 | async_t *async = SvASYNC_nrv (async_sv); |
438 | async_t *async = SvASYNC_nrv (async_sv); |
355 | |
439 | |
356 | for (i = AvFILLp (asyncs); i >= 0; --i) |
440 | for (i = AvFILLp (asyncs); i >= 0; --i) |
357 | if (AvARRAY (asyncs)[i] == async_sv) |
441 | if (AvARRAY (asyncs)[i] == async_sv) |
358 | { |
442 | { |
359 | if (i < AvFILLp (asyncs)) |
|
|
360 | AvARRAY (asyncs)[i] = AvARRAY (asyncs)[AvFILLp (asyncs)]; |
443 | AvARRAY (asyncs)[i] = AvARRAY (asyncs)[AvFILLp (asyncs)]; |
361 | |
444 | av_pop (asyncs); |
362 | assert (av_pop (asyncs) == async_sv); |
|
|
363 | goto found; |
445 | goto found; |
364 | } |
446 | } |
365 | |
447 | |
366 | if (!PL_dirty) |
448 | if (!PL_dirty) |
367 | warn ("Async::Interrupt::DESTROY could not find async object in list of asyncs, please report"); |
449 | warn ("Async::Interrupt::DESTROY could not find async object in list of asyncs, please report"); |
368 | |
450 | |
369 | found: |
451 | found: |
370 | |
452 | |
371 | if (async->signum) |
453 | if (async->signum) |
372 | { |
|
|
373 | #if _WIN32 |
|
|
374 | signal (async->signum, SIG_DFL); |
454 | setsig (async->signum, SIG_DFL); |
375 | #else |
|
|
376 | { |
|
|
377 | struct sigaction sa = { }; |
|
|
378 | sa.sa_handler = SIG_DFL; |
|
|
379 | sigaction (async->signum, &sa, 0); |
|
|
380 | } |
|
|
381 | #endif |
|
|
382 | } |
|
|
383 | |
455 | |
384 | if (!async->fh_r && async->ep.len) |
456 | if (!async->fh_r && async->ep.len) |
385 | s_epipe_destroy (&async->ep); |
457 | s_epipe_destroy (&async->ep); |
386 | |
458 | |
387 | SvREFCNT_dec (async->fh_r); |
459 | SvREFCNT_dec (async->fh_r); |
… | |
… | |
390 | SvREFCNT_dec (async->value); |
462 | SvREFCNT_dec (async->value); |
391 | |
463 | |
392 | Safefree (async); |
464 | Safefree (async); |
393 | } |
465 | } |
394 | |
466 | |
|
|
467 | SV * |
|
|
468 | sig2num (SV *signame_or_number) |
|
|
469 | ALIAS: |
|
|
470 | sig2num = 0 |
|
|
471 | sig2name = 1 |
|
|
472 | PROTOTYPE: $ |
|
|
473 | CODE: |
|
|
474 | { |
|
|
475 | int signum = s_signum (signame_or_number); |
|
|
476 | |
|
|
477 | if (signum < 0) |
|
|
478 | RETVAL = &PL_sv_undef; |
|
|
479 | else if (ix) |
|
|
480 | RETVAL = newSVpv (PL_sig_name [signum], 0); |
|
|
481 | else |
|
|
482 | RETVAL = newSViv (signum); |
|
|
483 | } |
|
|
484 | OUTPUT: |
|
|
485 | RETVAL |
|
|
486 | |
|
|
487 | MODULE = Async::Interrupt PACKAGE = Async::Interrupt::EventPipe PREFIX = s_epipe_ |
|
|
488 | |
|
|
489 | void |
|
|
490 | new (const char *klass) |
|
|
491 | PPCODE: |
|
|
492 | { |
|
|
493 | s_epipe *epp; |
|
|
494 | |
|
|
495 | Newz (0, epp, 1, s_epipe); |
|
|
496 | XPUSHs (sv_setref_iv (sv_newmortal (), klass, PTR2IV (epp))); |
|
|
497 | |
|
|
498 | if (s_epipe_new (epp) < 0) |
|
|
499 | croak ("Async::Interrupt::EventPipe: unable to create new event pipe"); |
|
|
500 | } |
|
|
501 | |
|
|
502 | void |
|
|
503 | filenos (s_epipe *epp) |
|
|
504 | PPCODE: |
|
|
505 | EXTEND (SP, 2); |
|
|
506 | PUSHs (sv_2mortal (newSViv (epp->fd [0]))); |
|
|
507 | PUSHs (sv_2mortal (newSViv (epp->fd [1]))); |
|
|
508 | |
|
|
509 | int |
|
|
510 | fileno (s_epipe *epp) |
|
|
511 | ALIAS: |
|
|
512 | fileno = 0 |
|
|
513 | fileno_r = 0 |
|
|
514 | fileno_w = 1 |
|
|
515 | CODE: |
|
|
516 | RETVAL = epp->fd [ix]; |
|
|
517 | OUTPUT: |
|
|
518 | RETVAL |
|
|
519 | |
|
|
520 | int |
|
|
521 | type (s_epipe *epp) |
|
|
522 | CODE: |
|
|
523 | RETVAL = epp->len; |
|
|
524 | OUTPUT: |
|
|
525 | RETVAL |
|
|
526 | |
|
|
527 | void |
|
|
528 | s_epipe_signal (s_epipe *epp) |
|
|
529 | |
|
|
530 | void |
|
|
531 | s_epipe_drain (s_epipe *epp) |
|
|
532 | |
|
|
533 | void |
|
|
534 | signal_func (s_epipe *epp) |
|
|
535 | ALIAS: |
|
|
536 | drain_func = 1 |
|
|
537 | PPCODE: |
|
|
538 | EXTEND (SP, 2); |
|
|
539 | PUSHs (sv_2mortal (newSViv (PTR2IV (ix ? s_epipe_drain : s_epipe_signal)))); |
|
|
540 | PUSHs (sv_2mortal (newSViv (PTR2IV (epp)))); |
|
|
541 | |
|
|
542 | void |
|
|
543 | s_epipe_wait (s_epipe *epp) |
|
|
544 | |
|
|
545 | void |
|
|
546 | DESTROY (s_epipe *epp) |
|
|
547 | CODE: |
|
|
548 | s_epipe_destroy (epp); |
|
|
549 | |