process_manager.c 18 KB

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