VMS/VMS_Implementations/VMS_impls/VMS__MC_shared_impl

view AnimationMaster.c @ 286:b02b34681414

VReo V2 -- saves checker and doer fn with the port, where triggered
author Sean Halle <seanhalle@yahoo.com>
date Wed, 10 Jul 2013 14:49:04 -0700
parents 72ffdb11ad8e
children 744b5ff9851e
line source
1 /*
2 * Copyright 2012 OpenSourceResearchInstitute.org
3 *
4 * Licensed under BSD
5 */
9 #include <stdio.h>
10 #include <stddef.h>
12 #include "PR.h"
14 //========================= Local Declarations ========================
15 inline PRProcess *
16 pickAProcess( AnimSlot *slot );
17 inline bool32
18 assignWork( PRProcess *process, AnimSlot *slot );
20 inline void
21 PRHandle__CreateTask( PRReqst *req, SlaveVP *slave );
22 inline void
23 PRHandle__EndTask( PRReqst *req, SlaveVP *slave );
24 inline void
25 PRHandle__CreateSlave(PRReqst *req, SlaveVP *slave );
26 inline void
27 PRHandle__EndSlave( PRReqst *req, SlaveVP *slave );
29 inline void
30 PRHandle__LangShutdown( PRReqst *req, SlaveVP *requestingSlv );
32 inline void
33 handleMakeProbe( PRServiceReq *langReq, PRLangEnv *protoLangEnv );
34 inline void
35 handleThrowException( PRServiceReq *langReq, PRLangEnv *protoLangEnv );
37 void
38 debug_print_req(AnimSlot *slot, PRReqst *req);
40 //===========================================================================
42 /*Note: there used to be a coreController that was another animation
43 * layer below both the masterVP and the slaveVPs.. in that case, the
44 * masterVP was a virtual processor whose processor-state was the same
45 * as a slaveVP's processor sate, both implemented as a SlaveVP struct.
46 * Have removed that, and
47 * changed the masterVP implementation. Instead of being a special version
48 * of a proto-runtime virtual processor, using the slaveVP stuct, the
49 * Master "virtual processor" is now implemented as a pthread pinned to
50 * a physical core.
51 */
53 /*This is the behavior of the Master. The physical processor switches
54 * between animating the master, and animating a slave. When a slave
55 * suspends, the PR "suspend" primitive switches the physical core over
56 * to animating the masterVP, which is implemented as a pinned pthread.
57 * This function is the behavior of that masterVP.
58 *This function's job is to manage processing
59 * requests and to trigger assignment of new work to the physical core,
60 * and to manage sharing the core among processes.
61 */
62 inline
63 bool32
64 masterFunction( AnimSlot *slot )
65 { //Scan the animation slots
66 int32 magicNumber;
67 SlaveVP *slave;
68 PRLangEnv *langEnv;
69 PRReqst *req;
70 PRProcess *process;
71 bool32 didAssignWork;
73 //Check if newly-done slave in slot, which will need request handled
74 //NOTE: left over from when had a coreController & MasterVP managed
75 // several slots
76 if( slot->workIsDone )
77 { slot->workIsDone = FALSE;
78 slot->needsWorkAssigned = TRUE;
80 //An Idle VP has no request to handle, so skip to assign..
81 if( slot->slaveAssignedToSlot->typeOfVP != IdleVP &&
82 slot->slaveAssignedToSlot->typeOfVP != ShutdownVP)
83 {
84 HOLISTIC__Record_AppResponder_start; //TODO: update to check which process for each slot
85 MEAS__startReqHdlr;
88 //process the request made by the slave (held inside slave struc)
89 slave = slot->slaveAssignedToSlot;
90 req = slave->request;
91 debug_print_req(slot, req);
93 //If the requesting slave is a slot slave, and request is not
94 // task-end, then turn it into a free task slave & continue
95 if( slave->typeOfVP == SlotTaskSlv && req->reqType != TaskEnd )
96 PR_int__replace_with_new_slot_slv( slave );
98 //Handle task create and end first -- they're special cases..
99 switch( req->reqType )
100 {
101 case TaskEnd:
102 { //do PR handler, which calls lang's hdlr and does recycle of
103 // free task slave if needed -- PR handler checks for free task Slv
104 PRHandle__EndTask( req, slave ); break;
105 }
106 case TaskCreate:
107 { //Do PR's create-task handler, which calls the lang's hdlr
108 // PR handler checks for free task Slv
109 PRHandle__CreateTask( req, slave ); break;
110 }
111 case SlvCreate: PRHandle__CreateSlave( req, slave ); break;
112 case SlvDissipate: PRHandle__EndSlave( req, slave ); break;
113 case Service: PRHandle__ServiceReq( slave ); break; //resumes into Service lang env
114 case Hardware: //for future expansion
115 case IO: //for future expansion
116 case OSCall: //for future expansion
117 PR_int__throw_exception("Not implemented", slave, NULL); break;
118 case LangShutdown: PRHandle__LangShutdown( req, slave ); break;
119 case Language: //normal lang request
120 { magicNumber = req->langMagicNumber;
121 langEnv = PR_PI__give_lang_env_for_slave( slave, magicNumber );
122 (*req->handler)( req->langReq, slave, langEnv );
123 }
124 }
126 MEAS__endReqHdlr;
127 HOLISTIC__Record_AppResponder_end;
128 }//if not idleVP
129 else if( slot->slaveAssignedToSlot->typeOfVP == ShutdownVP )
130 { //ShutdownVP used, essentially, as a flag, to cause terminate here
131 PR_int__release_master_lock();
132 terminateCoreCtlr( slot->slaveAssignedToSlot );
133 }
134 } //if have request to be handled
136 //NOTE: IF statement is leftover from when master managed many slots
137 didAssignWork = FALSE;
138 if( slot->needsWorkAssigned ) //can probably remove IF, now that only one slot
139 {
140 HOLISTIC__Record_Assigner_start;
142 //Pick a process to get this slot
143 process = pickAProcess( slot );
145 //Scan lang environs, looking for langEnv with ready work.
146 // call the Assigner for that lang Env, to get a slave for the slot
147 if( process != NULL )
148 { didAssignWork =
149 assignWork( process, slot );
150 }
151 HOLISTIC__Record_Assigner_end;
153 if( !didAssignWork ) //if no work assigned, be sure idle slave is in slot
154 { slot->slaveAssignedToSlot = _PRTopEnv->idleSlv[slot->coreSlotIsOn][0];
155 }
156 // fixme; //make into a loop that tries more processes if fails to assign
157 }//if slot needs slave assigned
159 return didAssignWork;
160 }
162 /*When several processes exist, use some pattern for picking one to give
163 * the animation slot to.
164 *First, it has to be a process that has work available.
165 *For now, just do a round-robin
166 */
167 inline
168 PRProcess *
169 pickAProcess( AnimSlot *slot )
170 { int32 idx;
171 PRProcess *process;
173 for( idx = _PRTopEnv->currProcessIdx; idx < _PRTopEnv->numProcesses; idx++)
174 {
175 process = _PRTopEnv->processes[ idx ];
176 if( process->numEnvsWithWork != 0 )
177 { _PRTopEnv->currProcessIdx = idx;
178 return process;
179 }
180 }
181 for( idx = 0; idx < _PRTopEnv->currProcessIdx; idx++)
182 {
183 process = _PRTopEnv->processes[ idx ];
184 if( process->numEnvsWithWork != 0 )
185 { _PRTopEnv->currProcessIdx = idx;
186 return process;
187 }
188 }
189 //none found
190 return NULL;
191 }
193 /*This does:
194 * 1) searches the language environments for one with work ready
195 * if finds one, asks its assigner to return work
196 * 2) checks what kind of work: new task, resuming task, resuming slave
197 * if new task, gets the slot slave and assigns task to it and returns slave
198 * else, gets the slave attached to the metaTask and returns that.
199 * 3) if no work found, then prune former task slaves waiting to be recycled.
200 * If no work and no slaves to prune, check for shutdown conditions.
201 *
202 * language env keeps its own work in its own structures, and has its own
203 * assigner. It chooses
204 * However, include a switch that switches-in an override assigner, which
205 * sees all the work in all the language env's. This is most likely
206 * generated by static tools and included in the executable. That means it
207 * has to be called via a registered pointer from here. The idea is that
208 * the static tools know which languages are grouped together.. and the
209 * override enables them to generate a custom assigner that uses info from
210 * all the languages in a unified way.. Don't really expect this to happen,
211 * but am making it possible.
212 */
213 inline
214 bool32
215 assignWork( PRProcess *process, AnimSlot *slot )
216 { int32 coreNum;
218 coreNum = slot->coreSlotIsOn;
220 if( process->overrideAssigner != NULL )
221 { if( process->numEnvsWithWork != 0 )
222 { (*process->overrideAssigner)( process, slot ); //calls PR fn that inserts work into slot
223 goto ReturnAfterAssigningWork; //quit for-loop, cause found work
224 }
225 else
226 goto NoWork;
227 }
229 //If here, then no override assigner, so search language envs for work
230 int32 envIdx, numEnvs; PRLangEnv **protoLangEnvsList, *protoLangEnv;
231 protoLangEnvsList = process->protoLangEnvsList;
232 numEnvs = process->numLangEnvs;
233 for( envIdx = 0; envIdx < numEnvs; envIdx++ ) //keep langEnvs in hash & array
234 { protoLangEnv = protoLangEnvsList[envIdx];
235 if( protoLangEnv->numReadyWork > 0 )
236 { bool32
237 didAssignWork =
238 (*protoLangEnv->workAssigner)( PR_int__give_lang_env(protoLangEnv), slot ); //assigner calls PR to put slave/task into slot
240 if(didAssignWork)
241 { protoLangEnv->numReadyWork -= 1;
242 if( protoLangEnv->numReadyWork == 0 )
243 { process->numEnvsWithWork -= 1;
244 }
245 goto ReturnAfterAssigningWork; //quit for-loop, 'cause found work
246 }
247 else
248 goto NoWork; //quit for-loop, cause found work
250 //NOTE: bad search alg -- should start where left off, then wrap around
251 }
252 }
253 //If reach here, then have searched all langEnv's & none have work..
255 NoWork: //No work, if end up here..
256 {
257 #ifdef HOLISTIC__TURN_ON_OBSERVE_UCC
258 returnSlv = process->idleSlv[coreNum][0]; //only one slot now, so [0]
260 //things that would normally happen in resume(), but idle VPs
261 // never go there
262 returnSlv->numTimesAssignedToASlot++; //gives each idle unit a unique ID
263 Unit newU;
264 newU.vp = returnSlv->slaveNum;
265 newU.task = returnSlv->numTimesAssignedToASlot;
266 addToListOfArrays(Unit,newU,process->unitList);
268 if (returnSlv->numTimesAssignedToASlot > 1) //make a dependency from prev idle unit
269 { Dependency newD; // to this one
270 newD.from_vp = returnSlv->slaveNum;
271 newD.from_task = returnSlv->numTimesAssignedToASlot - 1;
272 newD.to_vp = returnSlv->slaveNum;
273 newD.to_task = returnSlv->numTimesAssignedToASlot;
274 addToListOfArrays(Dependency, newD ,process->ctlDependenciesList);
275 }
276 #endif
277 HOLISTIC__Record_Assigner_end;
278 return FALSE;
279 }
281 ReturnAfterAssigningWork: //All paths goto here.. to provide single point for holistic..
282 {
283 HOLISTIC__Record_Assigner_end;
284 return TRUE;
285 }
286 }
289 //=================================
290 //===
291 //=
292 /*Create task is a special form, that has PR behavior in addition to plugin
293 * behavior. Master calls this first, and it then calls the plugin's
294 * create task handler.
295 *
296 *Note: the requesting slave must be either generic slave or free task slave
297 */
298 inline
299 void
300 PRHandle__CreateTask( PRReqst *req, SlaveVP *slave )
301 { PRMetaTask *protoMetaTask;
302 PRProcess *process;
303 PRLangEnv *protoLangEnv;
304 void *task;
306 process = slave->processSlaveIsIn;
308 protoLangEnv = PR_int__give_proto_lang_env_for_slave( slave,
309 req->langMagicNumber );
311 //Do the langlet's create-task handler, which keeps the task
312 // inside the langlet's lang env, but returns the langMetaTask
313 // so that PR can then put stuff into the prolog
314 //typedef void * (*CreateHandler)( void *, SlaveVP *, void * ); //req, slv, langEnv
315 //
316 task =
317 (*req->createHdlr)(req->langReq, slave, PR_int__give_lang_env(protoLangEnv) );
318 protoMetaTask = PR_int__give_prolog_of_lang_meta_task( task );
319 protoMetaTask->ID = req->ID; //may be NULL
320 protoMetaTask->topLevelFn = req->topLevelFn;
321 protoMetaTask->initData = req->initData;
322 protoMetaTask->processTaskIsIn = process;
324 process->numLiveTasks += 1;
325 protoLangEnv->numLiveWork += 1; //used in wait statements -- small added overhead
327 return;
328 }
330 /*When a task ends, have two scenarios: 1) task ran to completion, or 2) task
331 * has been suspended at some point in its code.
332 *For 1, just decr count of live tasks (and check for end condition) -- the
333 * master loop will decide what goes into the slot freed up by this task end,
334 * so, here, don't worry about assigning a new task to the slot slave.
335 *For 2, the task's slot slave has been converted to a free task slave, which
336 * now has nothing more to do, so send it to the recycle Q (which includes
337 * freeing all the langData and meta task structs alloc'd for it). Then
338 * decrement the live task count and check end condition.
339 *
340 *PR has to update count of live tasks, and check end of process condition.
341 * The "main" can invoke constructs that wait for a process to end, so when
342 * end detected, have to resume what's waiting..
343 *Thing is, that wait involves the main OS thread. That means
344 * PR internals have to do OS thread signaling. Want to do that in the
345 * core controller, which has the original stack of an OS thread. So the
346 * end process handling happens in the core controller.
347 *
348 *So here, when detect process end, signal to the core controller, which will
349 * then do the condition variable notify to the OS thread that's waiting.
350 *
351 *Note: slave may be either a slot slave or a free task slave.
352 */
353 inline
354 void
355 PRHandle__EndTask( PRReqst *req, SlaveVP *requestingSlv )
356 { void *langEnv;
357 PRLangEnv *protoLangEnv;
358 PRProcess *process;
359 void *langMetaTask;
361 process = requestingSlv->processSlaveIsIn;
362 langEnv = PR_int__give_lang_env_of_req( req, requestingSlv ); //magic num in req
363 protoLangEnv = PR_int__give_proto_lang_env( langEnv );
365 langMetaTask = PR_int__give_lang_meta_task_from_slave( requestingSlv, req->langMagicNumber);
367 //Do the langlet's request handler
368 //Want to keep PR structs hidden from plugin, so extract langReq..
369 //This is supposed to free any langlet-malloc'd mem, including meta task
370 (*req->handler)( req->langReq, requestingSlv, langEnv );
372 protoLangEnv->numLiveWork -= 1; //used in wait statements -- small added overhead
373 if( protoLangEnv->numLiveWork == 0 &&
374 numInPrivQ( protoLangEnv->waitingForWorkToEndQ ) > 0 )
375 { SlaveVP *
376 waitingSlave = readPrivQ( protoLangEnv->waitingForWorkToEndQ );
377 //can't resume into langlet that just ended its last work!
378 // and don't have env that the waiter was created in, so resume
379 // into PRServ env..
380 void *
381 resumeEnv = PR_PI__give_lang_env_from_process( process, PRServ_MAGIC_NUMBER );
382 while( waitingSlave != NULL )
383 { //resume a slave that was waiting for work in this env to finish
384 PR_PI__make_slave_ready( waitingSlave, resumeEnv );
385 //get next waiting slave, repeat..
386 waitingSlave = readPrivQ( protoLangEnv->waitingForWorkToEndQ );
387 }
388 }
391 //Now that the langlet's done with it, recycle the slave if it's a freeTaskSlv
392 if( requestingSlv->typeOfVP == FreeTaskSlv )
393 PR_int__recycle_slaveVP( requestingSlv ); //Doesn't decr num live slaves
395 process->numLiveTasks -= 1;
396 //NOTE: end-task is unrelated to work available (just in case wondering)
398 //check End Of Process Condition
399 if( process->numLiveTasks == 0 &&
400 process->numLiveGenericSlvs == 0 )
401 { //Tell the core controller to do wakeup of any waiting OS thread
402 PR_SS__end_process_normally( process );
403 }
404 }
408 /*This is first thing called when creating a slave.. it hands off to the
409 * langlet's creator, then adds updates of its own..
410 *
411 *There's a question of things like lang data, meta tasks, and such..
412 *In creator, only PR related things happen, and things for the langlet whose
413 * creator construct was used.
414 *
415 *Other langlets still get a chance to create langData -- but by registering a
416 * "createLangData" handler in the langEnv. When a construct of the langlet
417 * calls "PR__give_lang_data()", if there is no langData for that langlet,
418 * the PR will call the creator in the langlet's langEnv, place whatever it
419 * makes as the langData in that slave for that langlet, and return that langData
420 *
421 *So, as far as counting things, a langlet is only allowed to count creation
422 * of slaves it creates itself.. may have to change this later.. add a way for
423 * langlet to register a trigger Fn called each time a slave gets created..
424 * need more experience with what langlets will do at create time.. think Cilk
425 * has interesting create behavior.. not sure how that will differ in light
426 * of true tasks and langlet approach. Look at it after all done and start
427 * modifying the langs to be langlets..
428 *
429 *PR itself needs to create the slave, then update numLiveSlaves in process,
430 * copy processID from requestor to newly created
431 */
432 inline
433 void
434 PRHandle__CreateSlave( PRReqst *req, SlaveVP *slave )
435 { SlaveVP *newSlv;
436 PRProcess *process;
437 PRLangEnv *protoLangEnv;
439 process = slave->processSlaveIsIn;
440 protoLangEnv = PR_int__give_proto_lang_env_for_slave( slave, req->langMagicNumber );
442 //create handler, or a future request handler will call PR_PI__make_slave_ready
443 // which will in turn handle updating which langlets and which processes have
444 // work available.
445 //NOTE: create slv has diff prototype than standard reqst hdlr
446 newSlv =
447 (*req->createHdlr)(req->langReq, slave, PR_int__give_lang_env(protoLangEnv));
449 newSlv->typeOfVP = GenericSlv;
450 newSlv->processSlaveIsIn = process;
451 newSlv->ID = req->ID;
452 process->numLiveGenericSlvs += 1; //not same as work ready!
453 protoLangEnv->numLiveWork += 1; //used in wait statements -- small added overhead
454 }
456 /*The dissipate handler has to, update the number of slaves of the type, within
457 * the process, and call the langlet handler linked into the request,
458 * and after that returns, then call the PR function that frees the slave state
459 * (or recycles the slave).
460 *
461 *The PR function that frees the slave state has to also free all of the
462 * langData in the slave.. or else reset all of the langDatas.. by, say, marking
463 * them, then in PR__give_langData( magicNum ) call the langlet registered
464 * "resetLangData" Fn.
465 */
466 inline
467 void
468 PRHandle__EndSlave( PRReqst *req, SlaveVP *slave )
469 { PRProcess *process;
470 PRLangEnv *protoLangEnv;
472 process = slave->processSlaveIsIn;
474 //do the language's dissipate handler
475 protoLangEnv = PR_int__give_proto_lang_env_for_slave( slave, slave->request->langMagicNumber );
477 if(req->handler != NULL)
478 (*req->handler)( req->langReq, slave, PR_int__give_lang_env(protoLangEnv) );
480 protoLangEnv->numLiveWork -= 1; //used in wait statements -- small added overhead
481 if( protoLangEnv->numLiveWork == 0 &&
482 numInPrivQ( protoLangEnv->waitingForWorkToEndQ ) > 0 )
483 { SlaveVP *
484 waitingSlave = readPrivQ( protoLangEnv->waitingForWorkToEndQ );
485 //can't resume into langlet that just ended its last work!
486 // and don't have env that the waiter was created in, so resume
487 // into PRServ env..
488 void *
489 resumeEnv = PR_PI__give_lang_env_from_process( process, PRServ_MAGIC_NUMBER );
490 while( waitingSlave != NULL )
491 { //resume a slave that was waiting for work in this env to finish
492 PR_PI__make_slave_ready( waitingSlave, resumeEnv );
493 //get next waiting slave, repeat..
494 waitingSlave = readPrivQ( protoLangEnv->waitingForWorkToEndQ );
495 }
496 }
498 process->numLiveGenericSlvs -= 1;
499 PR_int__recycle_slaveVP( slave );
500 //NOTE: dissipate is unrelated to work available (just in case wondering)
502 //check End Of Process Condition
503 if( process->numLiveTasks == 0 &&
504 process->numLiveGenericSlvs == 0 )
505 PR_SS__end_process_normally( process );
506 }
508 //=======================
509 //===
510 //=
511 /*Langlet shutdown triggers this, which calls the registered shutdown
512 * handler for the langlet, and removes the lang's env from the process
513 */
514 inline
515 void
516 PRHandle__LangShutdown( PRReqst *req, SlaveVP *requestingSlv )
517 { void *langEnv;
518 PRLangEnv *protoLangEnv;
519 PRProcess *process;
521 process = requestingSlv->processSlaveIsIn;
522 protoLangEnv = PR_int__give_proto_lang_env_from_process( process, req->langMagicNumber );
523 langEnv = PR_int__give_lang_env( protoLangEnv );
525 //call the langlet's registered handler
526 (*protoLangEnv->shutdownHdlr)( langEnv );
528 PR_int__remove_lang_env_from_process_and_free( langEnv ); //removes from process and frees
530 PR_PI__resume_slave_in_PRServ( requestingSlv );
531 }
534 /*This is for OS requests and PR infrastructure requests, which are not
535 * part of the PRServ language -- this is for things that have to be in the
536 * infrastructure of PR itself, such as I/O requests, which have to go through
537 * pthreads inside the core controller..
538 *
539 *As of Jan 2013, doesn't do much of anything..
540 */
541 void inline
542 PRHandle__ServiceReq( SlaveVP *requestingSlv )
543 { PRReqst *req;
544 PRServiceReq *langReq;
545 PRLangEnv *protoLangEnv;
546 int32 magicNumber;
549 req = requestingSlv->request;
551 magicNumber = req->langMagicNumber;
552 protoLangEnv = PR_int__give_proto_lang_env_for_slave( requestingSlv, magicNumber );
554 langReq = PR_PI__take_lang_reqst_from(req);
555 if( langReq == NULL ) return;
556 switch( langReq->reqType ) //lang handlers are all in other file
557 {
558 case make_probe: handleMakeProbe( langReq, protoLangEnv );
559 break;
560 case throw_excp: handleThrowException( langReq, protoLangEnv );
561 break;
562 }
563 }
566 /*These handlers are special -- they don't belong to a language, because they
567 * deal with things internal to PR, so put them here..
568 */
569 inline
570 void
571 handleMakeProbe( PRServiceReq *langReq, PRLangEnv *protoLangEnv )
572 { IntervalProbe *newProbe;
574 newProbe = PR_int__malloc( sizeof(IntervalProbe) );
575 newProbe->nameStr = PR_int__strDup( langReq->nameStr );
576 newProbe->hist = NULL;
577 newProbe->schedChoiceWasRecorded = FALSE;
579 //This runs in masterVP, so no race-condition worries
580 //BUG: move to process
581 newProbe->probeID =
582 addToDynArray( newProbe, _PRTopEnv->dynIntervalProbesInfo );
584 langReq->requestingSlv->dataRetFromReq = newProbe;
586 (*protoLangEnv->makeSlaveReadyFn)( langReq->requestingSlv, PR_int__give_lang_env(protoLangEnv) );
587 }
589 inline
590 void
591 handleThrowException( PRServiceReq *langReq, PRLangEnv *protoLangEnv )
592 {
593 PR_int__throw_exception( langReq->msgStr, langReq->requestingSlv, langReq->exceptionData );
595 (*protoLangEnv->makeSlaveReadyFn)( langReq->requestingSlv, PR_int__give_lang_env(protoLangEnv) );
596 }
598 void
599 debug_print_req(AnimSlot *slot, PRReqst *req)
600 {
601 if(dbgMaster)
602 { printf("top handle request: %d | reqType: ", slot->coreSlotIsOn );
603 switch(req->reqType)
604 { case TaskCreate: printf("TaskCreate \n"); break;
605 case TaskEnd: printf("TaskEnd \n"); break;
606 case SlvCreate: printf("SlvCreate \n"); break;
607 case SlvDissipate: printf("SlvDissipate \n"); break;
608 case Language: printf("Language \n"); break;
609 case Service: printf("Service \n"); break;
610 case Hardware: printf("Hardware \n"); break;
611 case IO: printf("IO \n"); break;
612 case OSCall: printf("OSCall \n"); break;
613 case LangShutdown: printf("LangShutdown \n"); break;
614 case ProcessEnd: printf("ProcessEnd \n"); break;
615 case PRShutdown: printf("PRShutdown \n"); break;
616 }
617 fflush(stdin);
618 }
619 }