process_manager.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  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 stopping processes from templates dynamically.
  32. *
  33. * Synopsis:
  34. * process_manager()
  35. *
  36. * Description:
  37. * Manages processes. On deinitialization, initiates termination of all
  38. * contained processes and waits for them to terminate.
  39. *
  40. * Synopsis:
  41. * process_manager::start(name, string template_name, list args)
  42. *
  43. * Description:
  44. * Creates a new process from the template named template_name, with arguments args,
  45. * identified by name within the process manager. If a process with this name already exists
  46. * and is not being terminated, does nothing. If it is being terminated, it will be restarted
  47. * using the given parameters after it terminates.
  48. * The process can access objects as seen from the process_manager() statement via _caller.
  49. *
  50. * Synopsis:
  51. * process_manager::stop(name)
  52. *
  53. * Description:
  54. * Iinitiates termination of the process identified by name within the process manager.
  55. * If there is no such process, or the process is already being terminated, does nothing.
  56. */
  57. #include <stdlib.h>
  58. #include <string.h>
  59. #include <misc/offset.h>
  60. #include <misc/debug.h>
  61. #include <misc/strdup.h>
  62. #include <misc/balloc.h>
  63. #include <structure/LinkedList1.h>
  64. #include <ncd/NCDModule.h>
  65. #include <ncd/extra/value_utils.h>
  66. #include <generated/blog_channel_ncd_process_manager.h>
  67. #define ModuleLog(i, ...) NCDModuleInst_Backend_Log((i), BLOG_CURRENT_CHANNEL, __VA_ARGS__)
  68. #define RETRY_TIME 10000
  69. #define PROCESS_STATE_RUNNING 1
  70. #define PROCESS_STATE_STOPPING 2
  71. #define PROCESS_STATE_RESTARTING 3
  72. #define PROCESS_STATE_RETRYING 4
  73. struct instance {
  74. NCDModuleInst *i;
  75. LinkedList1 processes_list;
  76. int dying;
  77. };
  78. struct process {
  79. struct instance *manager;
  80. LinkedList1Node processes_list_node;
  81. BSmallTimer retry_timer; // running if state=retrying
  82. int state;
  83. NCD_string_id_t template_name;
  84. NCDValMem current_mem;
  85. NCDValSafeRef current_name;
  86. NCDValSafeRef current_args;
  87. NCDValMem next_mem; // next_* if state=restarting
  88. NCDValSafeRef next_name;
  89. NCDValSafeRef next_args;
  90. NCDModuleProcess module_process; // if state!=retrying
  91. };
  92. static struct process * find_process (struct instance *o, NCDValRef name);
  93. static int process_new (struct instance *o, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args);
  94. static void process_free (struct process *p);
  95. static void process_try (struct process *p);
  96. static void process_retry_timer_handler (BSmallTimer *retry_timer);
  97. static void process_module_process_handler_event (NCDModuleProcess *module_process, int event);
  98. static int process_module_process_func_getspecialobj (NCDModuleProcess *module_process, NCD_string_id_t name, NCDObject *out_object);
  99. static int process_module_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object);
  100. static void process_stop (struct process *p);
  101. static int process_restart (struct process *p, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args);
  102. static void instance_free (struct instance *o);
  103. enum {STRING_CALLER};
  104. static struct NCD_string_request strings[] = {
  105. {"_caller"}, {NULL}
  106. };
  107. static struct process * find_process (struct instance *o, NCDValRef name)
  108. {
  109. LinkedList1Node *n = LinkedList1_GetFirst(&o->processes_list);
  110. while (n) {
  111. struct process *p = UPPER_OBJECT(n, struct process, processes_list_node);
  112. ASSERT(p->manager == o)
  113. if (NCDVal_Compare(NCDVal_FromSafe(&p->current_mem, p->current_name), name) == 0) {
  114. return p;
  115. }
  116. n = LinkedList1Node_Next(n);
  117. }
  118. return NULL;
  119. }
  120. static int process_new (struct instance *o, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args)
  121. {
  122. ASSERT(!o->dying)
  123. ASSERT(!find_process(o, NCDVal_FromSafe(mem, name)))
  124. ASSERT(!NCDVal_IsInvalid(NCDVal_FromSafe(mem, name)))
  125. ASSERT(NCDVal_IsString(NCDVal_FromSafe(mem, template_name)))
  126. ASSERT(NCDVal_IsList(NCDVal_FromSafe(mem, args)))
  127. // allocate structure
  128. struct process *p = BAlloc(sizeof(*p));
  129. if (!p) {
  130. ModuleLog(o->i, BLOG_ERROR, "BAlloc failed");
  131. goto fail0;
  132. }
  133. // set manager
  134. p->manager = o;
  135. // insert to processes list
  136. LinkedList1_Append(&o->processes_list, &p->processes_list_node);
  137. // init retry timer
  138. BSmallTimer_Init(&p->retry_timer, process_retry_timer_handler);
  139. // init template name
  140. p->template_name = ncd_get_string_id(NCDVal_FromSafe(mem, template_name), o->i->params->iparams->string_index);
  141. if (p->template_name < 0) {
  142. ModuleLog(o->i, BLOG_ERROR, "ncd_get_string_id failed");
  143. goto fail1;
  144. }
  145. // init current mem as a copy of mem
  146. if (!NCDValMem_InitCopy(&p->current_mem, mem)) {
  147. ModuleLog(o->i, BLOG_ERROR, "NCDValMem_InitCopy failed");
  148. goto fail1;
  149. }
  150. // remember name and args
  151. p->current_name = name;
  152. p->current_args = args;
  153. // try starting it
  154. process_try(p);
  155. return 1;
  156. fail1:
  157. LinkedList1_Remove(&o->processes_list, &p->processes_list_node);
  158. BFree(p);
  159. fail0:
  160. return 0;
  161. }
  162. static void process_free (struct process *p)
  163. {
  164. struct instance *o = p->manager;
  165. // free current mem
  166. NCDValMem_Free(&p->current_mem);
  167. // free timer
  168. BReactor_RemoveSmallTimer(o->i->params->iparams->reactor, &p->retry_timer);
  169. // remove from processes list
  170. LinkedList1_Remove(&o->processes_list, &p->processes_list_node);
  171. // free structure
  172. BFree(p);
  173. }
  174. static void process_try (struct process *p)
  175. {
  176. struct instance *o = p->manager;
  177. ASSERT(!o->dying)
  178. ASSERT(!BSmallTimer_IsRunning(&p->retry_timer))
  179. ModuleLog(o->i, BLOG_INFO, "trying process");
  180. // init module process
  181. if (!NCDModuleProcess_InitId(&p->module_process, o->i, p->template_name, NCDVal_FromSafe(&p->current_mem, p->current_args), process_module_process_handler_event)) {
  182. ModuleLog(o->i, BLOG_ERROR, "NCDModuleProcess_Init failed");
  183. goto fail;
  184. }
  185. // set special objects function
  186. NCDModuleProcess_SetSpecialFuncs(&p->module_process, process_module_process_func_getspecialobj);
  187. // set state
  188. p->state = PROCESS_STATE_RUNNING;
  189. return;
  190. fail:
  191. // set timer
  192. BReactor_SetSmallTimer(o->i->params->iparams->reactor, &p->retry_timer, BTIMER_SET_RELATIVE, RETRY_TIME);
  193. // set state
  194. p->state = PROCESS_STATE_RETRYING;
  195. }
  196. static void process_retry_timer_handler (BSmallTimer *retry_timer)
  197. {
  198. struct process *p = UPPER_OBJECT(retry_timer, struct process, retry_timer);
  199. struct instance *o = p->manager;
  200. B_USE(o)
  201. ASSERT(p->state == PROCESS_STATE_RETRYING)
  202. ASSERT(!o->dying)
  203. // retry
  204. process_try(p);
  205. }
  206. void process_module_process_handler_event (NCDModuleProcess *module_process, int event)
  207. {
  208. struct process *p = UPPER_OBJECT(module_process, struct process, module_process);
  209. struct instance *o = p->manager;
  210. ASSERT(p->state != PROCESS_STATE_RETRYING)
  211. ASSERT(p->state != PROCESS_STATE_RESTARTING || !o->dying)
  212. ASSERT(!BSmallTimer_IsRunning(&p->retry_timer))
  213. switch (event) {
  214. case NCDMODULEPROCESS_EVENT_UP: {
  215. ASSERT(p->state == PROCESS_STATE_RUNNING)
  216. } break;
  217. case NCDMODULEPROCESS_EVENT_DOWN: {
  218. ASSERT(p->state == PROCESS_STATE_RUNNING)
  219. // allow process to continue
  220. NCDModuleProcess_Continue(&p->module_process);
  221. } break;
  222. case NCDMODULEPROCESS_EVENT_TERMINATED: {
  223. ASSERT(p->state == PROCESS_STATE_RESTARTING || p->state == PROCESS_STATE_STOPPING)
  224. // free module process
  225. NCDModuleProcess_Free(&p->module_process);
  226. if (p->state == PROCESS_STATE_RESTARTING) {
  227. // free current mem
  228. NCDValMem_Free(&p->current_mem);
  229. // move next mem/values over current mem/values
  230. p->current_mem = p->next_mem;
  231. p->current_name = p->next_name;
  232. p->current_args = p->next_args;
  233. // try starting it again
  234. process_try(p);
  235. return;
  236. }
  237. // free process
  238. process_free(p);
  239. // if manager is dying and there are no more processes, let it die
  240. if (o->dying && LinkedList1_IsEmpty(&o->processes_list)) {
  241. instance_free(o);
  242. }
  243. } break;
  244. }
  245. }
  246. static int process_module_process_func_getspecialobj (NCDModuleProcess *module_process, NCD_string_id_t name, NCDObject *out_object)
  247. {
  248. struct process *p = UPPER_OBJECT(module_process, struct process, module_process);
  249. ASSERT(p->state != PROCESS_STATE_RETRYING)
  250. if (name == strings[STRING_CALLER].id) {
  251. *out_object = NCDObject_Build(-1, p, NCDObject_no_getvar, process_module_process_caller_obj_func_getobj);
  252. return 1;
  253. }
  254. return 0;
  255. }
  256. static int process_module_process_caller_obj_func_getobj (const NCDObject *obj, NCD_string_id_t name, NCDObject *out_object)
  257. {
  258. struct process *p = NCDObject_DataPtr(obj);
  259. struct instance *o = p->manager;
  260. ASSERT(p->state != PROCESS_STATE_RETRYING)
  261. return NCDModuleInst_Backend_GetObj(o->i, name, out_object);
  262. }
  263. static void process_stop (struct process *p)
  264. {
  265. switch (p->state) {
  266. case PROCESS_STATE_RETRYING: {
  267. // free process
  268. process_free(p);
  269. } break;
  270. case PROCESS_STATE_RUNNING: {
  271. // request process to terminate
  272. NCDModuleProcess_Terminate(&p->module_process);
  273. // set state
  274. p->state = PROCESS_STATE_STOPPING;
  275. } break;
  276. case PROCESS_STATE_RESTARTING: {
  277. // free next mem
  278. NCDValMem_Free(&p->next_mem);
  279. // set state
  280. p->state = PROCESS_STATE_STOPPING;
  281. } break;
  282. case PROCESS_STATE_STOPPING: {
  283. // nothing to do
  284. } break;
  285. default: ASSERT(0);
  286. }
  287. }
  288. static int process_restart (struct process *p, NCDValMem *mem, NCDValSafeRef name, NCDValSafeRef template_name, NCDValSafeRef args)
  289. {
  290. struct instance *o = p->manager;
  291. ASSERT(!o->dying)
  292. ASSERT(p->state == PROCESS_STATE_STOPPING)
  293. ASSERT(NCDVal_Compare(NCDVal_FromSafe(mem, name), NCDVal_FromSafe(&p->current_mem, p->current_name)) == 0)
  294. ASSERT(NCDVal_IsString(NCDVal_FromSafe(mem, template_name)))
  295. ASSERT(NCDVal_IsList(NCDVal_FromSafe(mem, args)))
  296. // copy mem to next mem
  297. if (!NCDValMem_InitCopy(&p->next_mem, mem)) {
  298. ModuleLog(o->i, BLOG_ERROR, "NCDValMem_InitCopy failed");
  299. goto fail0;
  300. }
  301. // remember name and args to next
  302. p->next_name = name;
  303. p->next_args = args;
  304. // set state
  305. p->state = PROCESS_STATE_RESTARTING;
  306. return 1;
  307. fail0:
  308. return 0;
  309. }
  310. static void func_new (void *vo, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  311. {
  312. struct instance *o = vo;
  313. o->i = i;
  314. // check arguments
  315. if (!NCDVal_ListRead(params->args, 0)) {
  316. ModuleLog(o->i, BLOG_ERROR, "wrong arity");
  317. goto fail0;
  318. }
  319. // init processes list
  320. LinkedList1_Init(&o->processes_list);
  321. // set not dying
  322. o->dying = 0;
  323. // signal up
  324. NCDModuleInst_Backend_Up(o->i);
  325. return;
  326. fail0:
  327. NCDModuleInst_Backend_SetError(i);
  328. NCDModuleInst_Backend_Dead(i);
  329. }
  330. void instance_free (struct instance *o)
  331. {
  332. ASSERT(LinkedList1_IsEmpty(&o->processes_list))
  333. NCDModuleInst_Backend_Dead(o->i);
  334. }
  335. static void func_die (void *vo)
  336. {
  337. struct instance *o = vo;
  338. ASSERT(!o->dying)
  339. // request all processes to die
  340. LinkedList1Node *n = LinkedList1_GetFirst(&o->processes_list);
  341. while (n) {
  342. LinkedList1Node *next = LinkedList1Node_Next(n);
  343. struct process *p = UPPER_OBJECT(n, struct process, processes_list_node);
  344. process_stop(p);
  345. n = next;
  346. }
  347. // if there are no processes, die immediately
  348. if (LinkedList1_IsEmpty(&o->processes_list)) {
  349. instance_free(o);
  350. return;
  351. }
  352. // set dying
  353. o->dying = 1;
  354. }
  355. static void start_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  356. {
  357. // check arguments
  358. NCDValRef name_arg;
  359. NCDValRef template_name_arg;
  360. NCDValRef args_arg;
  361. if (!NCDVal_ListRead(params->args, 3, &name_arg, &template_name_arg, &args_arg)) {
  362. ModuleLog(i, BLOG_ERROR, "wrong arity");
  363. goto fail0;
  364. }
  365. if (!NCDVal_IsString(template_name_arg) || !NCDVal_IsList(args_arg)) {
  366. ModuleLog(i, BLOG_ERROR, "wrong type");
  367. goto fail0;
  368. }
  369. // signal up.
  370. // Do it before creating the process so that the process starts initializing before our own process continues.
  371. NCDModuleInst_Backend_Up(i);
  372. // get method object
  373. struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
  374. if (mo->dying) {
  375. ModuleLog(i, BLOG_INFO, "manager is dying, not creating process");
  376. } else {
  377. struct process *p = find_process(mo, name_arg);
  378. if (p && p->state != PROCESS_STATE_STOPPING) {
  379. ModuleLog(i, BLOG_INFO, "process already started");
  380. } else {
  381. if (p) {
  382. if (!process_restart(p, name_arg.mem, NCDVal_ToSafe(name_arg), NCDVal_ToSafe(template_name_arg), NCDVal_ToSafe(args_arg))) {
  383. ModuleLog(i, BLOG_ERROR, "failed to restart process");
  384. goto fail0;
  385. }
  386. } else {
  387. if (!process_new(mo, name_arg.mem, NCDVal_ToSafe(name_arg), NCDVal_ToSafe(template_name_arg), NCDVal_ToSafe(args_arg))) {
  388. ModuleLog(i, BLOG_ERROR, "failed to create process");
  389. goto fail0;
  390. }
  391. }
  392. }
  393. }
  394. return;
  395. fail0:
  396. NCDModuleInst_Backend_SetError(i);
  397. NCDModuleInst_Backend_Dead(i);
  398. }
  399. static void stop_func_new (void *unused, NCDModuleInst *i, const struct NCDModuleInst_new_params *params)
  400. {
  401. // check arguments
  402. NCDValRef name_arg;
  403. if (!NCDVal_ListRead(params->args, 1, &name_arg)) {
  404. ModuleLog(i, BLOG_ERROR, "wrong arity");
  405. goto fail0;
  406. }
  407. // signal up.
  408. // Do it before stopping the process so that the process starts terminating before our own process continues.
  409. NCDModuleInst_Backend_Up(i);
  410. // get method object
  411. struct instance *mo = NCDModuleInst_Backend_GetUser((NCDModuleInst *)params->method_user);
  412. if (mo->dying) {
  413. ModuleLog(i, BLOG_INFO, "manager is dying, not stopping process");
  414. } else {
  415. struct process *p = find_process(mo, name_arg);
  416. if (!(p && p->state != PROCESS_STATE_STOPPING)) {
  417. ModuleLog(i, BLOG_INFO, "process already stopped");
  418. } else {
  419. process_stop(p);
  420. }
  421. }
  422. return;
  423. fail0:
  424. NCDModuleInst_Backend_SetError(i);
  425. NCDModuleInst_Backend_Dead(i);
  426. }
  427. static struct NCDModule modules[] = {
  428. {
  429. .type = "process_manager",
  430. .func_new2 = func_new,
  431. .func_die = func_die,
  432. .alloc_size = sizeof(struct instance)
  433. }, {
  434. .type = "process_manager::start",
  435. .func_new2 = start_func_new
  436. }, {
  437. .type = "process_manager::stop",
  438. .func_new2 = stop_func_new
  439. }, {
  440. .type = NULL
  441. }
  442. };
  443. const struct NCDModuleGroup ncdmodule_process_manager = {
  444. .modules = modules,
  445. .strings = strings
  446. };