Преглед изворни кода

ncd: update process interface to allow implementation of the call() statement

ambrop7 пре 14 година
родитељ
комит
08a98240d3
5 измењених фајлова са 325 додато и 153 уклоњено
  1. 112 52
      ncd/NCDModule.c
  2. 21 11
      ncd/NCDModule.h
  3. 14 5
      ncd/modules/process_manager.c
  4. 44 7
      ncd/modules/synchronous_process.c
  5. 134 78
      ncd/ncd.c

+ 112 - 52
ncd/NCDModule.c

@@ -37,11 +37,15 @@
 #define STATE_UNDEAD 11
 
 #define PROCESS_STATE_INIT 1
-#define PROCESS_STATE_NORMAL 2
-#define PROCESS_STATE_DIE_PENDING 3
-#define PROCESS_STATE_DIE 4
-#define PROCESS_STATE_DEAD_PENDING 5
-#define PROCESS_STATE_DEAD 6
+#define PROCESS_STATE_DOWN 2
+#define PROCESS_STATE_UP_PENDING 3
+#define PROCESS_STATE_UP 4
+#define PROCESS_STATE_DOWN_PENDING 5
+#define PROCESS_STATE_DOWN_WAITING 6
+#define PROCESS_STATE_DOWN_CONTINUE_PENDING 7
+#define PROCESS_STATE_TERMINATING 8
+#define PROCESS_STATE_TERMINATED_PENDING 9
+#define PROCESS_STATE_TERMINATED 10
 
 static void frontend_event (NCDModuleInst *n, int event)
 {
@@ -95,28 +99,40 @@ static void clean_job_handler (NCDModuleInst *n)
     }
 }
 
-static void process_die_job_handler (NCDModuleProcess *o)
+static void process_event_job_handler (NCDModuleProcess *o)
 {
     DebugObject_Access(&o->d_obj);
-    ASSERT(o->state == PROCESS_STATE_DIE_PENDING)
     
-    // set state
-    o->state = PROCESS_STATE_DIE;
-    
-    o->interp_handler_die(o->interp_user);
-    return;
-}
-
-static void process_dead_job_handler (NCDModuleProcess *o)
-{
-    DebugObject_Access(&o->d_obj);
-    ASSERT(o->state == PROCESS_STATE_DEAD_PENDING)
-    
-    // set state
-    o->state = PROCESS_STATE_DEAD;
-    
-    o->handler_dead(o->user);
-    return;
+    switch (o->state) {
+        case PROCESS_STATE_DOWN_CONTINUE_PENDING: {
+            o->state = PROCESS_STATE_DOWN;
+            
+            o->interp_func_event(o->interp_user, NCDMODULEPROCESS_INTERP_EVENT_CONTINUE);
+        } break;
+        
+        case PROCESS_STATE_UP_PENDING: {
+            o->state = PROCESS_STATE_UP;
+            
+            o->handler_event(o->user, NCDMODULEPROCESS_EVENT_UP);
+            return;
+        } break;
+        
+        case PROCESS_STATE_DOWN_PENDING: {
+            o->state = PROCESS_STATE_DOWN_WAITING;
+            
+            o->handler_event(o->user, NCDMODULEPROCESS_EVENT_DOWN);
+            return;
+        } break;
+        
+        case PROCESS_STATE_TERMINATED_PENDING: {
+            o->state = PROCESS_STATE_TERMINATED;
+            
+            o->handler_event(o->user, NCDMODULEPROCESS_EVENT_TERMINATED);
+            return;
+        } break;
+        
+        default: ASSERT(0);
+    }
 }
 
 void NCDModuleInst_Init (NCDModuleInst *n, const struct NCDModule *m, NCDModuleInst *method_object, NCDValue *args, BReactor *reactor, BProcessManager *manager, NCDUdevManager *umanager, void *user,
@@ -379,77 +395,121 @@ void NCDModuleInst_Backend_SetError (NCDModuleInst *n)
     n->is_error = 1;
 }
 
-int NCDModuleProcess_Init (NCDModuleProcess *o, NCDModuleInst *n, const char *template_name, NCDValue args, void *user, NCDModuleProcess_handler_dead handler_dead)
+int NCDModuleProcess_Init (NCDModuleProcess *o, NCDModuleInst *n, const char *template_name, NCDValue args, void *user, NCDModuleProcess_handler_event handler_event)
 {
     DebugObject_Access(&n->d_obj);
     ASSERT(n->state == STATE_DOWN_PCLEAN || n->state == STATE_DOWN_UNCLEAN || n->state == STATE_DOWN_CLEAN ||
            n->state == STATE_UP || n->state == STATE_DOWN_DIE || n->state == STATE_UP_DIE ||
            n->state == STATE_DYING)
     ASSERT(NCDValue_Type(&args) == NCDVALUE_LIST)
+    ASSERT(handler_event)
     
     // init arguments
     o->n = n;
     o->user = user;
-    o->handler_dead = handler_dead;
+    o->handler_event = handler_event;
     
-    // clear interpreter data
-    o->interp_user = NULL;
-    o->interp_handler_die = NULL;
-    
-    // init jobs
-    BPending_Init(&o->die_job, BReactor_PendingGroup(n->reactor), (BPending_handler)process_die_job_handler, o);
-    BPending_Init(&o->dead_job, BReactor_PendingGroup(n->reactor), (BPending_handler)process_dead_job_handler, o);
+    // init event job
+    BPending_Init(&o->event_job, BReactor_PendingGroup(n->reactor), (BPending_handler)process_event_job_handler, o);
     
     // set state
     o->state = PROCESS_STATE_INIT;
     
+    // clear event func so we can assert it was set
+    o->interp_func_event = NULL;
+    
     // init interpreter part
     if (!(n->handler_initprocess(n->user, o, template_name, args))) {
-        goto fail0;
+        goto fail1;
     }
     
+    ASSERT(o->interp_func_event)
+    
     // set state
-    o->state = PROCESS_STATE_NORMAL;
+    o->state = PROCESS_STATE_DOWN;
     
     DebugObject_Init(&o->d_obj);
     return 1;
     
-fail0:
-    BPending_Free(&o->dead_job);
-    BPending_Free(&o->die_job);
+fail1:
+    BPending_Free(&o->event_job);
     return 0;
 }
 
 void NCDModuleProcess_Free (NCDModuleProcess *o)
 {
     DebugObject_Free(&o->d_obj);
-    ASSERT(o->state == PROCESS_STATE_DEAD)
+    ASSERT(o->state == PROCESS_STATE_TERMINATED)
     
-    // free jobs
-    BPending_Free(&o->dead_job);
-    BPending_Free(&o->die_job);
+    // free event job
+    BPending_Free(&o->event_job);
+}
+
+void NCDModuleProcess_Continue (NCDModuleProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state == PROCESS_STATE_DOWN_WAITING)
+    
+    o->state = PROCESS_STATE_DOWN;
+    
+    o->interp_func_event(o->interp_user, NCDMODULEPROCESS_INTERP_EVENT_CONTINUE);
 }
 
-void NCDModuleProcess_Die (NCDModuleProcess *o)
+void NCDModuleProcess_Terminate (NCDModuleProcess *o)
 {
     DebugObject_Access(&o->d_obj);
-    ASSERT(o->state == PROCESS_STATE_NORMAL)
+    ASSERT(o->state == PROCESS_STATE_DOWN || o->state == PROCESS_STATE_UP_PENDING ||
+           o->state == PROCESS_STATE_DOWN_CONTINUE_PENDING || o->state == PROCESS_STATE_UP ||
+           o->state == PROCESS_STATE_DOWN_PENDING || o->state == PROCESS_STATE_DOWN_WAITING)
+    
+    BPending_Unset(&o->event_job);
+    o->state = PROCESS_STATE_TERMINATING;
     
-    BPending_Set(&o->die_job);
-    o->state = PROCESS_STATE_DIE_PENDING;
+    o->interp_func_event(o->interp_user, NCDMODULEPROCESS_INTERP_EVENT_TERMINATE);
 }
 
-void NCDModuleProcess_Interp_SetHandlers (NCDModuleProcess *o, void *interp_user, NCDModuleProcess_interp_handler_die interp_handler_die)
+void NCDModuleProcess_Interp_SetHandlers (NCDModuleProcess *o, void *interp_user,  NCDModuleProcess_interp_func_event interp_func_event)
 {
+    ASSERT(interp_func_event)
+    
     o->interp_user = interp_user;
-    o->interp_handler_die = interp_handler_die;
+    o->interp_func_event = interp_func_event;
+}
+
+void NCDModuleProcess_Interp_Up (NCDModuleProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    ASSERT(o->state == PROCESS_STATE_DOWN)
+    
+    BPending_Set(&o->event_job);
+    o->state = PROCESS_STATE_UP_PENDING;
+}
+
+void NCDModuleProcess_Interp_Down (NCDModuleProcess *o)
+{
+    DebugObject_Access(&o->d_obj);
+    
+    switch (o->state) {
+        case PROCESS_STATE_UP_PENDING: {
+            BPending_Unset(&o->event_job);
+            BPending_Set(&o->event_job);
+            o->state = PROCESS_STATE_DOWN_CONTINUE_PENDING;
+        } break;
+        
+        case PROCESS_STATE_UP: {
+            BPending_Set(&o->event_job);
+            o->state = PROCESS_STATE_DOWN_PENDING;
+        } break;
+        
+        default: ASSERT(0);
+    }
 }
 
-void NCDModuleProcess_Interp_Dead (NCDModuleProcess *o)
+void NCDModuleProcess_Interp_Terminated (NCDModuleProcess *o)
 {
     DebugObject_Access(&o->d_obj);
-    ASSERT(o->state == PROCESS_STATE_DIE)
+    ASSERT(o->state == PROCESS_STATE_TERMINATING)
     
-    BPending_Set(&o->dead_job);
-    o->state = PROCESS_STATE_DEAD_PENDING;
+    BPending_Set(&o->event_job);
+    o->state = PROCESS_STATE_TERMINATED_PENDING;
 }

+ 21 - 11
ncd/NCDModule.h

@@ -46,8 +46,16 @@ typedef int (*NCDModule_handler_getvar) (void *user, const char *varname, NCDVal
 typedef struct NCDModuleInst_s * (*NCDModule_handler_getobj) (void *user, const char *objname);
 typedef int (*NCDModule_handler_initprocess) (void *user, struct NCDModuleProcess_s *p, const char *template_name, NCDValue args);
 
-typedef void (*NCDModuleProcess_handler_dead) (void *user);
-typedef void (*NCDModuleProcess_interp_handler_die) (void *user);
+#define NCDMODULEPROCESS_EVENT_UP 1
+#define NCDMODULEPROCESS_EVENT_DOWN 2
+#define NCDMODULEPROCESS_EVENT_TERMINATED 3
+
+typedef void (*NCDModuleProcess_handler_event) (void *user, int event);
+
+#define NCDMODULEPROCESS_INTERP_EVENT_CONTINUE 1
+#define NCDMODULEPROCESS_INTERP_EVENT_TERMINATE 2
+
+typedef void (*NCDModuleProcess_interp_func_event) (void *user, int event);
 
 struct NCDModule;
 
@@ -83,12 +91,11 @@ typedef struct NCDModuleInst_s {
 typedef struct NCDModuleProcess_s {
     NCDModuleInst *n;
     void *user;
-    NCDModuleProcess_handler_dead handler_dead;
-    void *interp_user;
-    NCDModuleProcess_interp_handler_die interp_handler_die;
-    BPending die_job;
-    BPending dead_job;
+    NCDModuleProcess_handler_event handler_event;
+    BPending event_job;
     int state;
+    void *interp_user;
+    NCDModuleProcess_interp_func_event interp_func_event;
     DebugObject d_obj;
 } NCDModuleProcess;
 
@@ -110,11 +117,14 @@ NCDModuleInst * NCDModuleInst_Backend_GetObj (NCDModuleInst *n, const char *objn
 void NCDModuleInst_Backend_Log (NCDModuleInst *n, int channel, int level, const char *fmt, ...);
 void NCDModuleInst_Backend_SetError (NCDModuleInst *n);
 
-int NCDModuleProcess_Init (NCDModuleProcess *o, NCDModuleInst *n, const char *template_name, NCDValue args, void *user, NCDModuleProcess_handler_dead handler_dead);
+int NCDModuleProcess_Init (NCDModuleProcess *o, NCDModuleInst *n, const char *template_name, NCDValue args, void *user, NCDModuleProcess_handler_event handler_event);
 void NCDModuleProcess_Free (NCDModuleProcess *o);
-void NCDModuleProcess_Die (NCDModuleProcess *o);
-void NCDModuleProcess_Interp_SetHandlers (NCDModuleProcess *o, void *interp_user, NCDModuleProcess_interp_handler_die interp_handler_die);
-void NCDModuleProcess_Interp_Dead (NCDModuleProcess *o);
+void NCDModuleProcess_Continue (NCDModuleProcess *o);
+void NCDModuleProcess_Terminate (NCDModuleProcess *o);
+void NCDModuleProcess_Interp_SetHandlers (NCDModuleProcess *o, void *interp_user,  NCDModuleProcess_interp_func_event interp_func_event);
+void NCDModuleProcess_Interp_Up (NCDModuleProcess *o);
+void NCDModuleProcess_Interp_Down (NCDModuleProcess *o);
+void NCDModuleProcess_Interp_Terminated (NCDModuleProcess *o);
 
 typedef int (*NCDModule_func_globalinit) (const struct NCDModuleInitParams params);
 typedef void (*NCDModule_func_globalfree) (void);

+ 14 - 5
ncd/modules/process_manager.c

@@ -27,7 +27,7 @@
  * Description: manages processes. On deinitialization, initiates termination of all
  *   contained processes and waits for them to terminate.
  * 
- * Synopsis: process_manager::start(string name, string template_name, list(string) args)
+ * Synopsis: process_manager::start(string name, string template_name, list args)
  * Description: creates a new process from the template named template_name, with arguments args,
  *   identified by name within the process manager. If a process with this name already exists
  *   and is not being terminated, does nothing. If it is being terminated, it will be restarted
@@ -201,11 +201,20 @@ void process_retry_timer_handler (struct process *p)
     process_try(p);
 }
 
-void process_module_process_handler_dead (struct process *p)
+void process_module_process_handler_event (struct process *p, int event)
 {
     struct instance *o = p->manager;
     ASSERT(p->have_module_process)
     
+    if (event == NCDMODULEPROCESS_EVENT_DOWN) {
+        // allow process to continue
+        NCDModuleProcess_Continue(&p->module_process);
+    }
+    
+    if (event != NCDMODULEPROCESS_EVENT_TERMINATED) {
+        return;
+    }
+    
     // free module process
     NCDModuleProcess_Free(&p->module_process);
     
@@ -251,8 +260,8 @@ void process_stop (struct process *p)
         case PROCESS_STATE_RUNNING: {
             ASSERT(p->have_module_process)
             
-            // request process to die
-            NCDModuleProcess_Die(&p->module_process);
+            // request process to terminate
+            NCDModuleProcess_Terminate(&p->module_process);
             
             // set state
             p->state = PROCESS_STATE_STOPPING;
@@ -314,7 +323,7 @@ void process_try (struct process *p)
     ModuleLog(o->i, BLOG_INFO, "trying process %s", p->name);
     
     // init module process
-    if (!NCDModuleProcess_Init(&p->module_process, o->i, p->params_template_name, p->params_args, p, (NCDModuleProcess_handler_dead)process_module_process_handler_dead)) {
+    if (!NCDModuleProcess_Init(&p->module_process, o->i, p->params_template_name, p->params_args, p, (NCDModuleProcess_handler_event)process_module_process_handler_event)) {
         ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
         
         // set timer

+ 44 - 7
ncd/modules/synchronous_process.c

@@ -24,7 +24,7 @@
  * Module which starts a process from a process template on initialization, and
  * stops it on deinitialization.
  * 
- * Synopsis: synchronous_process(string template_name, list(string) args)
+ * Synopsis: synchronous_process(string template_name, list args)
  * Description: on initialization, creates a new process from the template named
  *   template_name, with arguments args. On deinitialization, initiates termination
  *   of the process and waits for it to terminate.
@@ -38,17 +38,48 @@
 
 #define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
 
+#define STATE_WORKING 1
+#define STATE_UP 2
+#define STATE_TERMINATING 3
+
 struct instance {
     NCDModuleInst *i;
     NCDModuleProcess process;
+    int state;
 };
 
 static void instance_free (struct instance *o);
 
-static void process_handler_dead (struct instance *o)
+static void process_handler_event (struct instance *o, int event)
 {
-    // die now
-    instance_free(o);
+    switch (event) {
+        case NCDMODULEPROCESS_EVENT_UP: {
+            ASSERT(o->state == STATE_WORKING)
+            
+            // set state up
+            o->state = STATE_UP;
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_DOWN: {
+            ASSERT(o->state == STATE_UP)
+            
+            // process went down; allow it to continue immediately
+            NCDModuleProcess_Continue(&o->process);
+            
+            // set state working
+            o->state = STATE_WORKING;
+        } break;
+        
+        case NCDMODULEPROCESS_EVENT_TERMINATED: {
+            ASSERT(o->state == STATE_TERMINATING)
+            
+            // die finally
+            instance_free(o);
+            return;
+        } break;
+        
+        default: ASSERT(0);
+    }
 }
 
 static void func_new (NCDModuleInst *i)
@@ -88,12 +119,14 @@ static void func_new (NCDModuleInst *i)
     }
     
     // create process
-    if (!NCDModuleProcess_Init(&o->process, o->i, NCDValue_StringValue(template_name_arg), args, o, (NCDModuleProcess_handler_dead)process_handler_dead)) {
+    if (!NCDModuleProcess_Init(&o->process, o->i, NCDValue_StringValue(template_name_arg), args, o, (NCDModuleProcess_handler_event)process_handler_event)) {
         ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
         NCDValue_Free(&args);
         goto fail1;
     }
     
+    // set state working
+    o->state = STATE_WORKING;
     return;
     
 fail1:
@@ -119,9 +152,13 @@ void instance_free (struct instance *o)
 static void func_die (void *vo)
 {
     struct instance *o = vo;
+    ASSERT(o->state != STATE_TERMINATING)
+    
+    // request process to terminate
+    NCDModuleProcess_Terminate(&o->process);
     
-    // request process to die
-    NCDModuleProcess_Die(&o->process);
+    // set state terminating
+    o->state = STATE_TERMINATING;
 }
 
 static const struct NCDModule modules[] = {

+ 134 - 78
ncd/ncd.c

@@ -62,6 +62,11 @@
 #define SSTATE_DYING 3
 #define SSTATE_FORGOTTEN 4
 
+#define PSTATE_WORKING 1
+#define PSTATE_UP 2
+#define PSTATE_WAITING 3
+#define PSTATE_TERMINATING 4
+
 struct statement {
     char *object_name;
     char *method_name;
@@ -84,7 +89,7 @@ struct process {
     char *name;
     size_t num_statements;
     struct process_statement *statements;
-    int terminating;
+    int state;
     size_t ap;
     size_t fp;
     BTimer wait_timer;
@@ -170,7 +175,7 @@ static int process_statement_instance_handler_getvar (struct process_statement *
 static NCDModuleInst * process_statement_instance_handler_getobj (struct process_statement *ps, const char *objname);
 static int process_statement_instance_handler_initprocess (struct process_statement *ps, NCDModuleProcess *mp, const char *template_name, NCDValue args);
 static void process_statement_instance_logfunc (struct process_statement *ps);
-static void process_moduleprocess_handler_die (struct process *p);
+static void process_moduleprocess_func_event (struct process *p, int event);
 
 int main (int argc, char **argv)
 {
@@ -534,7 +539,9 @@ void signal_handler (void *unused)
         if (p->module_process) {
             continue;
         }
-        process_start_terminating(p);
+        if (p->state != PSTATE_TERMINATING) {
+            process_start_terminating(p);
+        }
     }
 }
 
@@ -678,7 +685,7 @@ int process_new (struct NCDConfig_interfaces *conf, NCDModuleProcess *module_pro
     
     // set module process handlers
     if (p->module_process) {
-        NCDModuleProcess_Interp_SetHandlers(p->module_process, p, (NCDModuleProcess_interp_handler_die)process_moduleprocess_handler_die);
+        NCDModuleProcess_Interp_SetHandlers(p->module_process, p, (NCDModuleProcess_interp_func_event)process_moduleprocess_func_event);
     }
     
     // set arguments
@@ -729,8 +736,8 @@ int process_new (struct NCDConfig_interfaces *conf, NCDModuleProcess *module_pro
         st = st->next;
     }
     
-    // set not terminating
-    p->terminating = 0;
+    // set state working
+    p->state = PSTATE_WORKING;
     
     // set AP=0
     p->ap = 0;
@@ -770,11 +777,11 @@ void process_free (struct process *p)
 {
     ASSERT(p->ap == 0)
     ASSERT(p->fp == 0)
-    ASSERT(p->terminating)
+    ASSERT(p->state == PSTATE_TERMINATING)
     
-    // inform module process that the process is dead
+    // inform module process that the process is terminated
     if (p->module_process) {
-        NCDModuleProcess_Interp_Dead(p->module_process);
+        NCDModuleProcess_Interp_Terminated(p->module_process);
     }
     
     // remove from processes list
@@ -804,12 +811,10 @@ void process_free (struct process *p)
 
 void process_start_terminating (struct process *p)
 {
-    if (p->terminating) {
-        return;
-    }
+    ASSERT(p->state != PSTATE_TERMINATING)
     
     // set terminating
-    p->terminating = 1;
+    p->state = PSTATE_TERMINATING;
     
     // schedule work
     process_schedule_work(p);
@@ -892,7 +897,11 @@ void process_work_job_handler (struct process *p)
     ASSERT(!BTimer_IsRunning(&p->wait_timer))
     ASSERT(!BPending_IsSet(&p->advance_job))
     
-    if (p->terminating) {
+    if (p->state == PSTATE_WAITING) {
+        return;
+    }
+    
+    if (p->state == PSTATE_TERMINATING) {
         if (p->fp == 0) {
             // finished retreating
             process_free(p);
@@ -901,67 +910,47 @@ void process_work_job_handler (struct process *p)
             if (terminating && LinkedList2_IsEmpty(&processes)) {
                 BReactor_Quit(&ss, 1);
             }
-        } else {
-            // order the last living statement to die, if needed
-            struct process_statement *ps = &p->statements[p->fp - 1];
-            ASSERT(ps->state != SSTATE_FORGOTTEN)
-            if (ps->state != SSTATE_DYING) {
-                process_statement_log(ps, BLOG_INFO, "killing");
-                
-                // order it to die
-                NCDModuleInst_Event(&ps->inst, NCDMODULE_TOEVENT_DIE);
-                
-                // set statement state DYING
-                ps->state = SSTATE_DYING;
-                
-                // update AP
-                if (p->ap > ps->i) {
-                    p->ap = ps->i;
-                }
-            }
-            
-            process_assert_pointers(p);
+            return;
         }
         
+        // order the last living statement to die, if needed
+        struct process_statement *ps = &p->statements[p->fp - 1];
+        ASSERT(ps->state != SSTATE_FORGOTTEN)
+        if (ps->state != SSTATE_DYING) {
+            process_statement_log(ps, BLOG_INFO, "killing");
+            
+            // order it to die
+            NCDModuleInst_Event(&ps->inst, NCDMODULE_TOEVENT_DIE);
+            
+            // set statement state DYING
+            ps->state = SSTATE_DYING;
+            
+            // update AP
+            if (p->ap > ps->i) {
+                p->ap = ps->i;
+            }
+        }
         return;
     }
     
-    if (p->ap == p->fp) {
-        if (p->ap == process_rap(p)) {
-            if (p->ap == p->num_statements) {
-                // all statements are up
-                process_log(p, BLOG_INFO, "victory");
-            } else {
-                struct process_statement *ps = &p->statements[p->ap];
-                ASSERT(ps->state == SSTATE_FORGOTTEN)
-                
-                // clear expired error
-                if (ps->have_error && ps->error_until <= btime_gettime()) {
-                    ps->have_error = 0;
-                }
-                
-                if (ps->have_error) {
-                    // next statement has error, wait
-                    process_statement_log(ps, BLOG_INFO, "waiting after error");
-                    BReactor_SetTimerAbsolute(&ss, &p->wait_timer, ps->error_until);
-                } else {
-                    // schedule advance
-                    BPending_Set(&p->advance_job);
-                }
-            }
-        } else {
-            ASSERT(p->ap > 0)
-            ASSERT(p->ap <= p->num_statements)
-            
-            struct process_statement *ps = &p->statements[p->ap - 1];
-            ASSERT(ps->state == SSTATE_CHILD)
-            
-            process_statement_log(ps, BLOG_INFO, "clean");
+    // process was up but is no longer?
+    if (p->state == PSTATE_UP && !(p->ap == process_rap(p) && p->ap == p->num_statements)) {
+        // if we have module process, wait for its permission to continue
+        if (p->module_process) {
+            // set module process down
+            NCDModuleProcess_Interp_Down(p->module_process);
             
-            // report clean
-            NCDModuleInst_Event(&ps->inst, NCDMODULE_TOEVENT_CLEAN);
+            // set state waiting
+            p->state = PSTATE_WAITING;
+            return;
         }
-    } else {
+        
+        // set state working
+        p->state = PSTATE_WORKING;
+    }
+    
+    // cleaning up?
+    if (p->ap < p->fp) {
         // order the last living statement to die, if needed
         struct process_statement *ps = &p->statements[p->fp - 1];
         if (ps->state != SSTATE_DYING) {
@@ -973,9 +962,59 @@ void process_work_job_handler (struct process *p)
             // set statement state DYING
             ps->state = SSTATE_DYING;
         }
+        return;
     }
     
-    process_assert_pointers(p);
+    // clean?
+    if (p->ap > process_rap(p)) {
+        ASSERT(p->ap > 0)
+        ASSERT(p->ap <= p->num_statements)
+        
+        struct process_statement *ps = &p->statements[p->ap - 1];
+        ASSERT(ps->state == SSTATE_CHILD)
+        
+        process_statement_log(ps, BLOG_INFO, "clean");
+        
+        // report clean
+        NCDModuleInst_Event(&ps->inst, NCDMODULE_TOEVENT_CLEAN);
+        return;
+    }
+    
+    // advancing?
+    if (p->ap < p->num_statements) {
+        ASSERT(p->state == PSTATE_WORKING)
+        struct process_statement *ps = &p->statements[p->ap];
+        ASSERT(ps->state == SSTATE_FORGOTTEN)
+        
+        // clear expired error
+        if (ps->have_error && ps->error_until <= btime_gettime()) {
+            ps->have_error = 0;
+        }
+        
+        if (ps->have_error) {
+            process_statement_log(ps, BLOG_INFO, "waiting after error");
+            
+            // set wait timer
+            BReactor_SetTimerAbsolute(&ss, &p->wait_timer, ps->error_until);
+        } else {
+            // schedule advance
+            BPending_Set(&p->advance_job);
+        }
+        return;
+    }
+    
+    // have we just finished?
+    if (p->state == PSTATE_WORKING) {
+        process_log(p, BLOG_INFO, "victory");
+        
+        // set module process up
+        if (p->module_process) {
+            NCDModuleProcess_Interp_Up(p->module_process);
+        }
+        
+        // set state up
+        p->state = PSTATE_UP;
+    }
 }
 
 void process_advance_job_handler (struct process *p)
@@ -987,7 +1026,7 @@ void process_advance_job_handler (struct process *p)
     ASSERT(!p->statements[p->ap].have_error)
     ASSERT(!BPending_IsSet(&p->work_job))
     ASSERT(!BTimer_IsRunning(&p->wait_timer))
-    ASSERT(!p->terminating)
+    ASSERT(p->state == PSTATE_WORKING)
     
     struct process_statement *ps = &p->statements[p->ap];
     ASSERT(ps->state == SSTATE_FORGOTTEN)
@@ -1091,7 +1130,7 @@ fail0:
     process_statement_set_error(ps);
     
     // schedule work to start the timer
-    BPending_Set(&p->work_job);
+    process_schedule_work(p);
 }
 
 void process_wait_timer_handler (struct process *p)
@@ -1103,7 +1142,7 @@ void process_wait_timer_handler (struct process *p)
     ASSERT(p->statements[p->ap].have_error)
     ASSERT(!BPending_IsSet(&p->work_job))
     ASSERT(!BPending_IsSet(&p->advance_job))
-    ASSERT(!p->terminating)
+    ASSERT(p->state == PSTATE_WORKING)
     
     process_log(p, BLOG_INFO, "retrying");
     
@@ -1412,13 +1451,30 @@ void process_statement_instance_logfunc (struct process_statement *ps)
     BLog_Append("module: ");
 }
 
-void process_moduleprocess_handler_die (struct process *p)
+void process_moduleprocess_func_event (struct process *p, int event)
 {
     ASSERT(p->module_process)
-    ASSERT(!p->terminating)
-    
-    process_log(p, BLOG_INFO, "process termination requested");
     
-    // start terminating
-    process_start_terminating(p);
+    switch (event) {
+        case NCDMODULEPROCESS_INTERP_EVENT_CONTINUE: {
+            ASSERT(p->state == PSTATE_WAITING)
+            
+            // set state working
+            p->state = PSTATE_WORKING;
+            
+            // schedule work
+            process_schedule_work(p);
+        } break;
+        
+        case NCDMODULEPROCESS_INTERP_EVENT_TERMINATE: {
+            ASSERT(p->state != PSTATE_TERMINATING)
+            
+            process_log(p, BLOG_INFO, "process termination requested");
+        
+            // start terminating
+            process_start_terminating(p);
+        } break;
+        
+        default: ASSERT(0);
+    }
 }