erlang各种有用的函数包括一些有用nif封装,还有一些性能测试case。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

564 line
16 KiB

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include "erl_nif.h"
  4. #include "cq_nif.h"
  5. /* #ifndef ERL_NIF_DIRTY_SCHEDULER_SUPPORT
  6. # error Requires dirty schedulers
  7. #endif */
  8. ERL_NIF_TERM
  9. mk_atom(ErlNifEnv* env, const char* atom)
  10. {
  11. ERL_NIF_TERM ret;
  12. if(!enif_make_existing_atom(env, atom, &ret, ERL_NIF_LATIN1))
  13. return enif_make_atom(env, atom);
  14. return ret;
  15. }
  16. ERL_NIF_TERM
  17. mk_error(ErlNifEnv* env, const char* mesg)
  18. {
  19. return enif_make_tuple2(env, mk_atom(env, "error"), mk_atom(env, mesg));
  20. }
  21. static ERL_NIF_TERM
  22. queue_new(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
  23. {
  24. cq_t *q = enif_alloc_resource(CQ_RESOURCE, sizeof(cq_t));
  25. if (q == NULL)
  26. return mk_error(env, "priv_alloc_error");
  27. ERL_NIF_TERM ret = enif_make_resource(env, q);
  28. /* enif_release_resource(ret); */
  29. uint32_t queue_id = 0;
  30. uint32_t queue_size = 0;
  31. uint32_t overflow_size = 0;
  32. if (!enif_get_uint(env, argv[0], &queue_id) ||
  33. !enif_get_uint(env, argv[1], &queue_size) ||
  34. !enif_get_uint(env, argv[2], &overflow_size))
  35. return mk_error(env, "badarg");
  36. if (queue_id > 8)
  37. return mk_error(env, "bad_queue_id");
  38. /* TODO: Check that queue_size is power of 2 */
  39. if (QUEUES[queue_id] != NULL)
  40. return mk_error(env, "queue_id_already_exists");
  41. q->id = queue_id;
  42. q->queue_size = queue_size;
  43. q->overflow_size = overflow_size;
  44. q->tail = 0;
  45. q->head = 0;
  46. q->slots_states = calloc(q->queue_size, CACHE_LINE_SIZE);
  47. q->slots_terms = calloc(q->queue_size, CACHE_LINE_SIZE);
  48. q->slots_envs = calloc(q->queue_size, CACHE_LINE_SIZE);
  49. q->overflow_terms = calloc(q->overflow_size, CACHE_LINE_SIZE);
  50. q->overflow_envs = calloc(q->queue_size, CACHE_LINE_SIZE);
  51. q->push_queue = new_queue();
  52. q->pop_queue = new_queue();
  53. /* TODO: Check calloc return */
  54. for (int i = 0; i < q->queue_size; i++) {
  55. ErlNifEnv *slot_env = enif_alloc_env();
  56. q->slots_envs[i*CACHE_LINE_SIZE] = slot_env;
  57. //q->overflow_envs[i*CACHE_LINE_SIZE] = (ErlNifEnv *) enif_alloc_env();
  58. }
  59. QUEUES[q->id] = q;
  60. return enif_make_tuple2(env, mk_atom(env, "ok"), ret);
  61. }
  62. static ERL_NIF_TERM
  63. queue_free(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
  64. {
  65. uint32_t queue_id = 0;
  66. if (!enif_get_uint(env, argv[0], &queue_id))
  67. return mk_error(env, "badarg");
  68. if (queue_id > 8)
  69. return mk_error(env, "badarg");
  70. cq_t *q = QUEUES[queue_id];
  71. if (q == NULL)
  72. return mk_error(env, "bad_queue_id");
  73. /* TODO: Free all the things! */
  74. QUEUES[queue_id] = NULL;
  75. return enif_make_atom(env, "ok");
  76. }
  77. /* Push to the head of the queue. */
  78. static ERL_NIF_TERM
  79. queue_push(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
  80. {
  81. uint32_t queue_id = 0;
  82. if (!enif_get_uint(env, argv[0], &queue_id))
  83. return mk_error(env, "badarg");
  84. if (queue_id > 8)
  85. return mk_error(env, "badarg");
  86. /* Load the queue */
  87. cq_t *q = QUEUES[queue_id];
  88. if (q == NULL)
  89. return mk_error(env, "bad_queue_id");
  90. if (q->id != queue_id)
  91. return mk_error(env, "not_identical_queue_id");
  92. for (int i = 0; i < q->queue_size; i++) {
  93. fprintf(stderr, "queue slot %d, index %d, state %d\n",
  94. i, i*CACHE_LINE_SIZE, q->slots_states[i*CACHE_LINE_SIZE]);
  95. }
  96. /* If there's consumers waiting, the queue must be empty and we
  97. should directly pick a consumer to notify. */
  98. ErlNifPid *waiting_consumer;
  99. int dequeue_ret = dequeue(q->pop_queue, &waiting_consumer);
  100. if (dequeue_ret) {
  101. ErlNifEnv *msg_env = enif_alloc_env();
  102. ERL_NIF_TERM copy = enif_make_copy(msg_env, argv[1]);
  103. ERL_NIF_TERM tuple = enif_make_tuple2(msg_env, mk_atom(env, "pop"), copy);
  104. if (enif_send(env, waiting_consumer, msg_env, tuple)) {
  105. enif_free_env(msg_env);
  106. return mk_atom(env, "ok");
  107. } else {
  108. return mk_error(env, "notify_failed");
  109. }
  110. }
  111. /* Increment head and attempt to claim the slot by marking it as
  112. busy. This ensures no other thread will attempt to modify this
  113. slot. If we cannot lock it, another thread must have */
  114. uint64_t head = __sync_add_and_fetch(&q->head, 1);
  115. size_t size = q->queue_size;
  116. while (1) {
  117. uint64_t index = SLOT_INDEX(head, size);
  118. uint64_t ret = __sync_val_compare_and_swap(&q->slots_states[index],
  119. STATE_EMPTY,
  120. STATE_WRITE);
  121. switch (ret) {
  122. case STATE_EMPTY:
  123. head = __sync_add_and_fetch(&q->head, 1);
  124. case STATE_WRITE:
  125. /* We acquired the write lock, go ahead with the write. */
  126. break;
  127. case STATE_FULL:
  128. /* We have caught up with the tail and the buffer is
  129. full. Block the producer until a consumer reads the
  130. item. */
  131. return mk_error(env, "full_not_implemented");
  132. }
  133. }
  134. /* If head catches up with tail, the queue is full. Add to
  135. overflow instead */
  136. /* Copy term to slot-specific temporary process env. */
  137. ERL_NIF_TERM copy = enif_make_copy(q->slots_envs[SLOT_INDEX(head, size)], argv[1]);
  138. q->slots_terms[SLOT_INDEX(head, size)] = copy;
  139. __sync_synchronize(); /* Or compiler memory barrier? */
  140. /* TODO: Do we need to collect garbage? */
  141. /* Mark the slot ready to be consumed */
  142. if (__sync_bool_compare_and_swap(&q->slots_states[SLOT_INDEX(head, size)],
  143. STATE_WRITE,
  144. STATE_FULL)) {
  145. return mk_atom(env, "ok");
  146. } else {
  147. return mk_error(env, "could_not_update_slots_after_insert");
  148. }
  149. }
  150. static ERL_NIF_TERM
  151. queue_async_pop(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
  152. {
  153. /* Load queue */
  154. uint32_t queue_id = 0;
  155. if (!enif_get_uint(env, argv[0], &queue_id))
  156. return mk_error(env, "badarg");
  157. if (queue_id > 8)
  158. return mk_error(env, "badarg");
  159. cq_t *q = QUEUES[queue_id];
  160. if (q == NULL)
  161. return mk_error(env, "bad_queue_id");
  162. if (q->id != queue_id)
  163. return mk_error(env, "not_identical_queue_id");
  164. uint64_t qsize = q->queue_size;
  165. uint64_t tail = q->tail;
  166. uint64_t num_busy = 0;
  167. /* Walk the buffer starting the tail position until we are either
  168. able to consume a term or find an empty slot. */
  169. while (1) {
  170. uint64_t index = SLOT_INDEX(tail, qsize);
  171. uint64_t ret = __sync_val_compare_and_swap(&q->slots_states[index],
  172. STATE_FULL,
  173. STATE_READ);
  174. if (ret == STATE_READ) {
  175. /* We were able to mark the term as read in progress. We
  176. now have an exclusive lock. */
  177. break;
  178. } else if (ret == STATE_WRITE) {
  179. /* We found an item with a write in progress. If that
  180. thread progresses, it will eventually mark the slot as
  181. full. We can spin until that happens.
  182. This can take an arbitrary amount of time and multiple
  183. reading threads will compete for the same slot.
  184. Instead we add the caller to the queue of blocking
  185. consumers. When the next producer comes it will "help"
  186. this thread by calling enif_send on the current
  187. in-progress term *and* handle it's own terms. If
  188. there's no new push to the queue, this will block
  189. forever. */
  190. return mk_atom(env, "write_in_progress_not_implemented");
  191. } else if (ret == STATE_EMPTY) {
  192. /* We found an empty item. Queue must be empty. Add
  193. calling Erlang consumer process to queue of waiting
  194. processes. When the next producer comes along, it first
  195. checks the waiting consumers and calls enif_send
  196. instead of writing to the slots. */
  197. ErlNifPid *pid = enif_alloc(sizeof(ErlNifPid));
  198. pid = enif_self(env, pid);
  199. enqueue(q->pop_queue, pid);
  200. return mk_atom(env, "wait_for_msg");
  201. } else {
  202. tail = __sync_add_and_fetch(&q->tail, 1);
  203. }
  204. }
  205. /* Copy term into calling process env. The NIF env can now be
  206. gargbage collected. */
  207. ERL_NIF_TERM copy = enif_make_copy(env, q->slots_terms[SLOT_INDEX(tail, qsize)]);
  208. /* Mark the slot as free. Note: We don't increment the tail
  209. position here, as another thread also walking the buffer might
  210. have incremented it multiple times */
  211. q->slots_terms[SLOT_INDEX(tail, qsize)] = 0;
  212. if (__sync_bool_compare_and_swap(&q->slots_states[SLOT_INDEX(tail, qsize)],
  213. STATE_READ,
  214. STATE_EMPTY)) {
  215. return enif_make_tuple2(env, mk_atom(env, "ok"), copy);
  216. } else {
  217. return mk_error(env, "could_not_update_slots_after_pop");
  218. }
  219. }
  220. static ERL_NIF_TERM
  221. queue_debug(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
  222. {
  223. uint32_t queue_id = 0;
  224. if (!enif_get_uint(env, argv[0], &queue_id))
  225. return mk_error(env, "badarg");
  226. if (queue_id > 8)
  227. return mk_error(env, "badarg");
  228. cq_t *q = QUEUES[queue_id];
  229. if (q == NULL)
  230. return mk_error(env, "bad_queue_id");
  231. ERL_NIF_TERM *slots_states = enif_alloc(sizeof(ERL_NIF_TERM) * q->queue_size);
  232. ERL_NIF_TERM *slots_terms = enif_alloc(sizeof(ERL_NIF_TERM) * q->queue_size);
  233. for (int i = 0; i < q->queue_size; i++) {
  234. slots_states[i] = enif_make_int(env, q->slots_states[i * CACHE_LINE_SIZE]);
  235. if (q->slots_terms[i * CACHE_LINE_SIZE] == 0) {
  236. slots_terms[i] = mk_atom(env, "null");
  237. } else {
  238. slots_terms[i] = enif_make_copy(env, q->slots_terms[i * CACHE_LINE_SIZE]);
  239. }
  240. }
  241. return enif_make_tuple4(env,
  242. enif_make_uint64(env, q->tail),
  243. enif_make_uint64(env, q->head),
  244. enif_make_list_from_array(env, slots_states, q->queue_size),
  245. enif_make_list_from_array(env, slots_terms, q->queue_size));
  246. }
  247. static ERL_NIF_TERM
  248. queue_debug_poppers(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
  249. {
  250. uint32_t queue_id = 0;
  251. if (!enif_get_uint(env, argv[0], &queue_id))
  252. return mk_error(env, "badarg");
  253. if (queue_id > 8)
  254. return mk_error(env, "badarg");
  255. cq_t *q = QUEUES[queue_id];
  256. if (q == NULL)
  257. return mk_error(env, "bad_queue_id");
  258. uint64_t pop_queue_size = 0;
  259. cq_node_t *node = q->pop_queue->head;
  260. if (node->value == NULL) {
  261. node = node->next;
  262. node = Q_PTR(node);
  263. }
  264. while (node != NULL) {
  265. pop_queue_size++;
  266. node = node->next;
  267. node = Q_PTR(node);
  268. }
  269. ERL_NIF_TERM *pop_queue_pids = enif_alloc(sizeof(ERL_NIF_TERM) * pop_queue_size);
  270. node = q->pop_queue->head;
  271. node = Q_PTR(node);
  272. if (node->value == NULL) {
  273. node = node->next;
  274. node = Q_PTR(node);
  275. }
  276. uint64_t i = 0;
  277. while (node != NULL) {
  278. if (node->value == 0) {
  279. pop_queue_pids[i] = mk_atom(env, "null");
  280. }
  281. else {
  282. pop_queue_pids[i] = enif_make_pid(env, node->value);
  283. }
  284. i++;
  285. node = node->next;
  286. node = Q_PTR(node);
  287. }
  288. ERL_NIF_TERM list = enif_make_list_from_array(env, pop_queue_pids, pop_queue_size);
  289. enif_free(pop_queue_pids);
  290. return list;
  291. }
  292. static ERL_NIF_TERM
  293. print_bits(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
  294. {
  295. uint64_t *p1 = malloc(8);
  296. *p1 = 0;
  297. for (int bit = 63; bit >= 0; bit--) {
  298. uint64_t power = 1 << bit;
  299. //uint64_t byte = *p1;
  300. uint64_t byte = p1;
  301. fprintf(stderr, "%d", (byte & power) >> bit);
  302. }
  303. fprintf(stderr, "\n");
  304. //enif_free(p1);
  305. return mk_atom(env, "ok");
  306. }
  307. void free_resource(ErlNifEnv* env, void* arg)
  308. {
  309. //cq_t *cq = (cq_t *) arg;
  310. fprintf(stderr, "free_resource\n");
  311. }
  312. cq_queue_t * new_queue()
  313. {
  314. cq_queue_t *queue = enif_alloc(sizeof(cq_queue_t));
  315. cq_node_t *node = enif_alloc(sizeof(cq_node_t));
  316. node->next = NULL;
  317. //node->env = NULL;
  318. node->value = NULL;
  319. queue->head = node;
  320. queue->tail = node;
  321. return queue;
  322. }
  323. void enqueue(cq_queue_t *queue, ErlNifPid *pid)
  324. {
  325. cq_node_t *node = enif_alloc(sizeof(cq_node_t));
  326. //node->env = enif_alloc_env();
  327. //node->term = enif_make_copy(node->env, term);
  328. node->value = pid;
  329. node->next = NULL;
  330. fprintf(stderr, "node %lu\n", node);
  331. cq_node_t *tail = NULL;
  332. uint64_t tail_count = 0;
  333. while (1) {
  334. tail = queue->tail;
  335. cq_node_t *tail_ptr = Q_PTR(tail);
  336. tail_count = Q_COUNT(tail);
  337. cq_node_t *next = tail->next;
  338. cq_node_t *next_ptr = Q_PTR(next);
  339. uint64_t next_count = Q_COUNT(next);
  340. if (tail == queue->tail) {
  341. fprintf(stderr, "tail == queue->tail\n");
  342. if (next_ptr == NULL) {
  343. fprintf(stderr, "next_ptr == NULL\n");
  344. if (__sync_bool_compare_and_swap(&tail_ptr->next,
  345. next,
  346. Q_SET_COUNT(node, next_count+1)))
  347. fprintf(stderr, "CAS(tail_ptr->next, next, (node, next_count+1)) -> true\n");
  348. break;
  349. } else {
  350. __sync_bool_compare_and_swap(&queue->tail,
  351. tail,
  352. Q_SET_COUNT(next_ptr, next_count+1));
  353. fprintf(stderr, "CAS(queue->tail, tail, (next_ptr, next_count+1))\n");
  354. }
  355. }
  356. }
  357. cq_node_t *node_with_count = Q_SET_COUNT(node, tail_count+1);
  358. int ret = __sync_bool_compare_and_swap(&queue->tail,
  359. tail,
  360. node_with_count);
  361. fprintf(stderr, "CAS(queue->tail, tail, %lu) -> %d\n", node_with_count, ret);
  362. }
  363. int dequeue(cq_queue_t *queue, ErlNifPid **pid)
  364. {
  365. fprintf(stderr, "dequeue\n");
  366. cq_node_t *head, *head_ptr, *tail, *tail_ptr, *next, *next_ptr;
  367. while (1) {
  368. head = queue->head;
  369. head_ptr = Q_PTR(head);
  370. tail = queue->tail;
  371. tail_ptr = Q_PTR(tail);
  372. next = head->next;
  373. next_ptr = Q_PTR(next);
  374. fprintf(stderr, "head %lu, tail %lu, next %lu\n", head, tail, next);
  375. if (head == queue->head) {
  376. if (head_ptr == tail_ptr) {
  377. if (next_ptr == NULL) {
  378. return 0; /* Queue is empty */
  379. }
  380. fprintf(stderr, "CAS(queue->tail, tail, (next_ptr, tail+1))\n");
  381. __sync_bool_compare_and_swap(&queue->tail,
  382. tail,
  383. Q_SET_COUNT(next_ptr, Q_COUNT(tail)+1));
  384. } else {
  385. fprintf(stderr, "next->value %lu\n", next_ptr->value);
  386. *pid = next_ptr->value;
  387. fprintf(stderr, "CAS(queue->head, head, (next_ptr, head+1))\n");
  388. if (__sync_bool_compare_and_swap(&queue->head,
  389. head,
  390. Q_SET_COUNT(next_ptr, Q_COUNT(head)+1)))
  391. break;
  392. }
  393. }
  394. }
  395. // free pid
  396. //enif_free(Q_PTR(head));
  397. return 1;
  398. }
  399. int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) {
  400. /* Initialize global array mapping id to cq_t ptr */
  401. QUEUES = (cq_t **) calloc(8, sizeof(cq_t **));
  402. if (QUEUES == NULL)
  403. return -1;
  404. ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);
  405. CQ_RESOURCE = enif_open_resource_type(env, "cq", "cq",
  406. &free_resource, flags, NULL);
  407. if (CQ_RESOURCE == NULL)
  408. return -1;
  409. return 0;
  410. }
  411. static ErlNifFunc nif_funcs[] = {
  412. {"new" , 3, queue_new},
  413. {"free" , 1, queue_free},
  414. {"push" , 2, queue_push},
  415. {"async_pop", 1, queue_async_pop},
  416. {"debug" , 1, queue_debug},
  417. {"debug_poppers", 1, queue_debug_poppers},
  418. {"print_bits", 0, print_bits}
  419. };
  420. ERL_NIF_INIT(cq, nif_funcs, load, NULL, NULL, NULL);