process_manager.c 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. /**
  2. * @file process_manager.c
  3. * @author Ambroz Bizjak <ambrop7@gmail.com>
  4. *
  5. * @section LICENSE
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions are met:
  9. * 1. Redistributions of source code must retain the above copyright
  10. * notice, this list of conditions and the following disclaimer.
  11. * 2. Redistributions in binary form must reproduce the above copyright
  12. * notice, this list of conditions and the following disclaimer in the
  13. * documentation and/or other materials provided with the distribution.
  14. * 3. Neither the name of the author nor the
  15. * names of its contributors may be used to endorse or promote products
  16. * derived from this software without specific prior written permission.
  17. *
  18. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  19. * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  20. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  21. * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
  22. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  23. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  25. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  26. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  27. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  28. *
  29. * @section DESCRIPTION
  30. *
  31. * Module which allows starting and controlling template processes using an imperative
  32. * interface.
  33. *
  34. * Synopsis:
  35. * process_manager()
  36. *
  37. * Description:
  38. * Represents a set of managed processes. Each process has a "name", which is a value
  39. * that uniquely identifies it within its process manager.
  40. * When deinitialization is requested, requests termination of all managed processes
  41. * and waits for all of them to terminate before deinitializing.
  42. * Managed processes can access objects as seen from the process_manager() statement
  43. * via the special _caller object.
  44. *
  45. * Synopsis:
  46. * process_manager::start(name, string template_name, list args)
  47. * process_manager::start(string template_name, list args)
  48. *
  49. * Description:
  50. * Creates a new process from the template named 'template_name', with arguments 'args',
  51. * identified by 'name' within the process manager. If the two-argument form of start() is
  52. * used, the process does not have a name, and cannot be imperatively stopped using
  53. * stop().
  54. * If a process with this name already exists and is not being terminated, does nothing.
  55. * If it exists and is being terminated, it will be restarted using the given parameters
  56. * after it terminates. If the process does not exist, it is created immediately, and the
  57. * immediate effects of the process being created happnen before the immediate effects of
  58. * the start() statement going up.
  59. *
  60. * Synopsis:
  61. * process_manager::stop(name)
  62. *
  63. * Description:
  64. * Initiates termination of the process identified by 'name' within the process manager.
  65. * If there is no such process, or the process is already being terminated, does nothing.
  66. * If the process does exist and is not already being terminated, termination of the
  67. * process is requested, and the immediate effects of the termination request happen
  68. * before the immediate effects of the stop() statement going up.
  69. */
  70. #include <stdlib.h>
  71. #include <string.h>
  72. #include <misc/offset.h>
  73. #include <misc/debug.h>
  74. #include <misc/strdup.h>
  75. #include <misc/balloc.h>
  76. #include <structure/LinkedList1.h>
  77. #include <ncd/module_common.h>
  78. #include <generated/blog_channel_ncd_process_manager.h>
  79. #define RETRY_TIME 10000
  80. #define PROCESS_STATE_RUNNING 1
  81. #define PROCESS_STATE_STOPPING 2
  82. #define PROCESS_STATE_RESTARTING 3
  83. #define PROCESS_STATE_RETRYING 4
  84. struct instance {
  85. NCDModuleInst *i;
  86. LinkedList1 processes_list;
  87. int dying;
  88. };
  89. struct process {
  90. struct instance *manager;
  91. LinkedList1Node processes_list_node;
  92. BSmallTimer retry_timer; // running if state=retrying
  93. int state;
  94. NCD_string_id_t template_name;
  95. NCDValMem current_mem;
  96. NCDValSafeRef current_name;
  97. NCDValSafeRef current_args;
  98. NCDValMem next_mem; // next_* if state=restarting
  99. NCDValSafeRef next_name;
  100. NCDValSafeRef next_args;
  101. NCDModuleProcess module_process; // if state!=retrying
  102. };
  103. static struct process * find_process (struct instance *o, NCDValRef name);
  104. static int process_new (struct instance *o, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args);
  105. static void process_free (struct process *p);
  106. static void process_try (struct process *p);
  107. static void process_retry_timer_handler (BSmallTimer *retry_timer);
  108. static void process_module_process_handler_event (NCDModuleProcess *module_process, int event);
  109. static int process_module_process_func_getspecialobj (NCDModuleProcess *module_process, NCD_string_id_t name, NCDObject *out_object);
  110. static int process_module_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
  111. static void process_stop (struct process *p);
  112. static int process_restart (struct process *p, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args);
  113. static void instance_free (struct instance *o);
  114. static struct process * find_process (struct instance *o, NCDValRef name)
  115. {
  116. ASSERT(!NCDVal_IsInvalid(name))
  117. LinkedList1Node *n = LinkedList1_GetFirst(&o->processes_list);
  118. while (n) {
  119. struct process *p = UPPER_OBJECT(n, struct process, processes_list_node);
  120. ASSERT(p->manager == o)
  121. if (!NCDVal_IsInvalid(NCDVal_FromSafe(&p->current_mem, p->current_name)) && NCDVal_Compare(NCDVal_FromSafe(&p->current_mem, p->current_name), name) == 0) {
  122. return p;
  123. }
  124. n = LinkedList1Node_Next(n);
  125. }
  126. return NULL;
  127. }
  128. static int process_new (struct instance *o, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args)
  129. {
  130. ASSERT(!o->dying)
  131. ASSERT(NCDVal_IsInvalid(NCDVal_FromSafe(mem, name)) || !find_process(o, NCDVal_FromSafe(mem, name)))
  132. ASSERT(NCDVal_IsString(NCDVal_FromSafe(mem, template_name)))
  133. ASSERT(NCDVal_IsList(NCDVal_FromSafe(mem, args)))
  134. // allocate structure
  135. struct process *p = BAlloc(sizeof(*p));
  136. if (!p) {
  137. ModuleLog(o->i, BLOG_ERROR, "BAlloc failed");
  138. goto fail0;
  139. }
  140. // set manager
  141. p->manager = o;
  142. // insert to processes list
  143. LinkedList1_Append(&o->processes_list, &p->processes_list_node);
  144. // init retry timer
  145. BSmallTimer_Init(&p->retry_timer, process_retry_timer_handler);
  146. // init template name
  147. p->template_name = ncd_get_string_id(NCDVal_FromSafe(mem, template_name));
  148. if (p->template_name < 0) {
  149. ModuleLog(o->i, BLOG_ERROR, "ncd_get_string_id failed");
  150. goto fail1;
  151. }
  152. // init current mem as a copy of mem
  153. if (!NCDValMem_InitCopy(&p->current_mem, mem)) {
  154. ModuleLog(o->i, BLOG_ERROR, "NCDValMem_InitCopy failed");
  155. goto fail1;
  156. }
  157. // remember name and args
  158. p->current_name = name;
  159. p->current_args = args;
  160. // try starting it
  161. process_try(p);
  162. return 1;
  163. fail1:
  164. LinkedList1_Remove(&o->processes_list, &p->processes_list_node);
  165. BFree(p);
  166. fail0:
  167. return 0;
  168. }
  169. static void process_free (struct process *p)
  170. {
  171. struct instance *o = p->manager;
  172. // free current mem
  173. NCDValMem_Free(&p->current_mem);
  174. // free timer
  175. BReactor_RemoveSmallTimer(o->i->params->iparams->reactor, &p->retry_timer);
  176. // remove from processes list
  177. LinkedList1_Remove(&o->processes_list, &p->processes_list_node);
  178. // free structure
  179. BFree(p);
  180. }
  181. static void process_try (struct process *p)
  182. {
  183. struct instance *o = p->manager;
  184. ASSERT(!o->dying)
  185. ASSERT(!BSmallTimer_IsRunning(&p->retry_timer))
  186. // init module process
  187. if (!NCDModuleProcess_InitId(&p->module_process, o->i, p->template_name, NCDVal_FromSafe(&p->current_mem, p->current_args), process_module_process_handler_event)) {
  188. ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
  189. goto fail;
  190. }
  191. // set special objects function
  192. NCDModuleProcess_SetSpecialFuncs(&p->module_process, process_module_process_func_getspecialobj);
  193. // set state
  194. p->state = PROCESS_STATE_RUNNING;
  195. return;
  196. fail:
  197. // set timer
  198. BReactor_SetSmallTimer(o->i->params->iparams->reactor, &p->retry_timer, BTIMER_SET_RELATIVE, RETRY_TIME);
  199. // set state
  200. p->state = PROCESS_STATE_RETRYING;
  201. }
  202. static void process_retry_timer_handler (BSmallTimer *retry_timer)
  203. {
  204. struct process *p = UPPER_OBJECT(retry_timer, struct process, retry_timer);
  205. struct instance *o = p->manager;
  206. B_USE(o)
  207. ASSERT(p->state == PROCESS_STATE_RETRYING)
  208. ASSERT(!o->dying)
  209. // retry
  210. process_try(p);
  211. }
  212. void process_module_process_handler_event (NCDModuleProcess *module_process, int event)
  213. {
  214. struct process *p = UPPER_OBJECT(module_process, struct process, module_process);
  215. struct instance *o = p->manager;
  216. ASSERT(p->state != PROCESS_STATE_RETRYING)
  217. ASSERT(p->state != PROCESS_STATE_RESTARTING || !o->dying)
  218. ASSERT(!BSmallTimer_IsRunning(&p->retry_timer))
  219. switch (event) {
  220. case NCDMODULEPROCESS_EVENT_UP: {
  221. ASSERT(p->state == PROCESS_STATE_RUNNING)
  222. } break;
  223. case NCDMODULEPROCESS_EVENT_DOWN: {
  224. ASSERT(p->state == PROCESS_STATE_RUNNING)
  225. // allow process to continue
  226. NCDModuleProcess_Continue(&p->module_process);
  227. } break;
  228. case NCDMODULEPROCESS_EVENT_TERMINATED: {
  229. ASSERT(p->state == PROCESS_STATE_RESTARTING || p->state == PROCESS_STATE_STOPPING)
  230. // free module process
  231. NCDModuleProcess_Free(&p->module_process);
  232. if (p->state == PROCESS_STATE_RESTARTING) {
  233. // free current mem
  234. NCDValMem_Free(&p->current_mem);
  235. // move next mem/values over current mem/values
  236. p->current_mem = p->next_mem;
  237. p->current_name = p->next_name;
  238. p->current_args = p->next_args;
  239. // try starting it again
  240. process_try(p);
  241. return;
  242. }
  243. // free process
  244. process_free(p);
  245. // if manager is dying and there are no more processes, let it die
  246. if (o->dying && LinkedList1_IsEmpty(&o->processes_list)) {
  247. instance_free(o);
  248. }
  249. } break;
  250. }
  251. }
  252. static int process_module_process_func_getspecialobj (NCDModuleProcess *module_process, NCD_string_id_t name, NCDObject *out_object)
  253. {
  254. struct process *p = UPPER_OBJECT(module_process, struct process, module_process);
  255. ASSERT(p->state != PROCESS_STATE_RETRYING)
  256. if (name == NCD_STRING_CALLER) {
  257. *out_object = NCDObject_Build(-1, p, NCDObject_no_getvar, process_module_process_caller_obj_func_getobj);
  258. return 1;
  259. }
  260. return 0;
  261. }
  262. static int process_module_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
  263. {
  264. struct process *p = NCDObject_DataPtr(obj);
  265. struct instance *o = p->manager;
  266. ASSERT(p->state != PROCESS_STATE_RETRYING)
  267. return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
  268. }
  269. static void process_stop (struct process *p)
  270. {
  271. switch (p->state) {
  272. case PROCESS_STATE_RETRYING: {
  273. // free process
  274. process_free(p);
  275. } break;
  276. case PROCESS_STATE_RUNNING: {
  277. // request process to terminate
  278. NCDModuleProcess_Terminate(&p->module_process);
  279. // set state
  280. p->state = PROCESS_STATE_STOPPING;
  281. } break;
  282. case PROCESS_STATE_RESTARTING: {
  283. // free next mem
  284. NCDValMem_Free(&p->next_mem);
  285. // set state
  286. p->state = PROCESS_STATE_STOPPING;
  287. } break;
  288. case PROCESS_STATE_STOPPING: {
  289. // nothing to do
  290. } break;
  291. default: ASSERT(0);
  292. }
  293. }
  294. static int process_restart (struct process *p, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args)
  295. {
  296. struct instance *o = p->manager;
  297. ASSERT(!o->dying)
  298. ASSERT(p->state == PROCESS_STATE_STOPPING)
  299. ASSERT(!NCDVal_IsInvalid(NCDVal_FromSafe(&p->current_mem, p->current_name)) || NCDVal_IsInvalid(NCDVal_FromSafe(mem, name)))
  300. ASSERT(NCDVal_IsInvalid(NCDVal_FromSafe(&p->current_mem, p->current_name)) || NCDVal_Compare(NCDVal_FromSafe(mem, name), NCDVal_FromSafe(&p->current_mem, p->current_name)) == 0)
  301. ASSERT(NCDVal_IsString(NCDVal_FromSafe(mem, template_name)))
  302. ASSERT(NCDVal_IsList(NCDVal_FromSafe(mem, args)))
  303. // copy mem to next mem
  304. if (!NCDValMem_InitCopy(&p->next_mem, mem)) {
  305. ModuleLog(o->i, BLOG_ERROR, "NCDValMem_InitCopy failed");
  306. goto fail0;
  307. }
  308. // remember name and args to next
  309. p->next_name = name;
  310. p->next_args = args;
  311. // set state
  312. p->state = PROCESS_STATE_RESTARTING;
  313. return 1;
  314. fail0:
  315. return 0;
  316. }
  317. static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  318. {
  319. struct instance *o = vo;
  320. o->i = i;
  321. // check arguments
  322. if (!NCDVal_ListRead(params->args, 0)) {
  323. ModuleLog(o->i, BLOG_ERROR, "wrong arity");
  324. goto fail0;
  325. }
  326. // init processes list
  327. LinkedList1_Init(&o->processes_list);
  328. // set not dying
  329. o->dying = 0;
  330. // signal up
  331. NCDModuleInst_Backend_Up(o->i);
  332. return;
  333. fail0:
  334. NCDModuleInst_Backend_DeadError(i);
  335. }
  336. void instance_free (struct instance *o)
  337. {
  338. ASSERT(LinkedList1_IsEmpty(&o->processes_list))
  339. NCDModuleInst_Backend_Dead(o->i);
  340. }
  341. static void func_die (void *vo)
  342. {
  343. struct instance *o = vo;
  344. ASSERT(!o->dying)
  345. // request all processes to die
  346. LinkedList1Node *n = LinkedList1_GetFirst(&o->processes_list);
  347. while (n) {
  348. LinkedList1Node *next = LinkedList1Node_Next(n);
  349. struct process *p = UPPER_OBJECT(n, struct process, processes_list_node);
  350. process_stop(p);
  351. n = next;
  352. }
  353. // if there are no processes, die immediately
  354. if (LinkedList1_IsEmpty(&o->processes_list)) {
  355. instance_free(o);
  356. return;
  357. }
  358. // set dying
  359. o->dying = 1;
  360. }
  361. static void start_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  362. {
  363. // check arguments
  364. NCDValRef name_arg = NCDVal_NewInvalid();
  365. NCDValRef template_name_arg;
  366. NCDValRef args_arg;
  367. if (!NCDVal_ListRead(params->args, 2, &template_name_arg, &args_arg) &&
  368. !NCDVal_ListRead(params->args, 3, &name_arg, &template_name_arg, &args_arg)
  369. ) {
  370. ModuleLog(i, BLOG_ERROR, "wrong arity");
  371. goto fail0;
  372. }
  373. if (!NCDVal_IsString(template_name_arg) || !NCDVal_IsList(args_arg)) {
  374. ModuleLog(i, BLOG_ERROR, "wrong type");
  375. goto fail0;
  376. }
  377. // signal up.
  378. // Do it before creating the process so that the process starts initializing before our own process continues.
  379. NCDModuleInst_Backend_Up(i);
  380. // get method object
  381. struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
  382. if (!mo->dying) {
  383. struct process *p = (NCDVal_IsInvalid(name_arg) ? NULL : find_process(mo, name_arg));
  384. if (!p || p->state == PROCESS_STATE_STOPPING) {
  385. if (p) {
  386. if (!process_restart(p, args_arg.mem, NCDVal_ToSafe(name_arg), NCDVal_ToSafe(template_name_arg), NCDVal_ToSafe(args_arg))) {
  387. ModuleLog(i, BLOG_ERROR, "failed to restart process");
  388. goto fail0;
  389. }
  390. } else {
  391. if (!process_new(mo, args_arg.mem, NCDVal_ToSafe(name_arg), NCDVal_ToSafe(template_name_arg), NCDVal_ToSafe(args_arg))) {
  392. ModuleLog(i, BLOG_ERROR, "failed to create process");
  393. goto fail0;
  394. }
  395. }
  396. }
  397. }
  398. return;
  399. fail0:
  400. NCDModuleInst_Backend_DeadError(i);
  401. }
  402. static void stop_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  403. {
  404. // check arguments
  405. NCDValRef name_arg;
  406. if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
  407. ModuleLog(i, BLOG_ERROR, "wrong arity");
  408. goto fail0;
  409. }
  410. // signal up.
  411. // Do it before stopping the process so that the process starts terminating before our own process continues.
  412. NCDModuleInst_Backend_Up(i);
  413. // get method object
  414. struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
  415. if (!mo->dying) {
  416. struct process *p = find_process(mo, name_arg);
  417. if (p && p->state != PROCESS_STATE_STOPPING) {
  418. process_stop(p);
  419. }
  420. }
  421. return;
  422. fail0:
  423. NCDModuleInst_Backend_DeadError(i);
  424. }
  425. static struct NCDModule modules[] = {
  426. {
  427. .type = "process_manager",
  428. .func_new2 = func_new,
  429. .func_die = func_die,
  430. .alloc_size = sizeof(struct instance)
  431. }, {
  432. .type = "process_manager::start",
  433. .func_new2 = start_func_new
  434. }, {
  435. .type = "process_manager::stop",
  436. .func_new2 = stop_func_new
  437. }, {
  438. .type = NULL
  439. }
  440. };
  441. const struct NCDModuleGroup ncdmodule_process_manager = {
  442. .modules = modules
  443. };