{"action":"created","comment":{"url":"https://api.github.com/repos/Shopify/semian/pulls/comments/42783724","id":42783724,"diff_hunk":"@@ -0,0 +1,108 @@\n+#include \"semian_shared_memory_object.h\"\n+\n+typedef struct {\n+  int value;\n+} semian_atomic_int;\n+\n+static void semian_atomic_integer_bind_init_fn (size_t byte_size, void *dest, void *prev_data, size_t prev_data_byte_size, int prev_mem_attach_count);\n+static VALUE semian_atomic_integer_bind_init_fn_wrapper(VALUE self);\n+static VALUE semian_atomic_integer_get_value(VALUE self);\n+static VALUE semian_atomic_integer_set_value(VALUE self, VALUE num);\n+static VALUE semian_atomic_integer_increase_by(VALUE self, VALUE num);\n+\n+static void\n+semian_atomic_integer_bind_init_fn (size_t byte_size, void *dest, void *prev_data, size_t prev_data_byte_size, int prev_mem_attach_count)\n+{\n+  semian_atomic_int *ptr = dest;\n+  semian_atomic_int *old = prev_data;\n+  if (prev_mem_attach_count){\n+    if (prev_data){\n+      ptr->value = old->value;\n+    } // else copy nothing, data is same size and no need to copy\n+  } else {\n+    ptr->value=0;\n+  }\n+}\n+\n+static VALUE\n+semian_atomic_integer_bind_init_fn_wrapper(VALUE self)\n+{\n+  semian_shm_object *ptr;\n+  TypedData_Get_Struct(self, semian_shm_object, &semian_shm_object_type, ptr);\n+  ptr->object_init_fn = &semian_atomic_integer_bind_init_fn;\n+  return self;\n+}\n+\n+static VALUE\n+semian_atomic_integer_get_value(VALUE self)\n+{\n+  semian_shm_object *ptr;\n+  TypedData_Get_Struct(self, semian_shm_object, &semian_shm_object_type, ptr);\n+\n+  // check shared memory for NULL\n+  if (0 == ptr->shm_address)\n+    return Qnil;\n+\n+  semian_shm_object_check_and_resize_if_needed(self);\n+  if (!semian_shm_object_lock(self))\n+    return Qnil;\n+\n+  int value = ((semian_atomic_int *)(ptr->shm_address))->value;\n+  semian_shm_object_unlock(self);\n+  return INT2NUM(value);\n+}\n+\n+static VALUE\n+semian_atomic_integer_set_value(VALUE self, VALUE num)\n+{\n+  semian_shm_object *ptr;\n+  TypedData_Get_Struct(self, semian_shm_object, &semian_shm_object_type, ptr);\n+\n+  if (0 == ptr->shm_address)\n+    return Qnil;\n+\n+  if (TYPE(num) != T_FIXNUM && TYPE(num) != T_FLOAT)\n+    return Qnil;\n+  semian_shm_object_check_and_resize_if_needed(self);\n+  if (!semian_shm_object_lock(self))\n+    return Qnil;\n+\n+  ((semian_atomic_int *)(ptr->shm_address))->value = NUM2INT(num);\n+\n+  semian_shm_object_unlock(self);\n+  return num;\n+}\n+\n+static VALUE\n+semian_atomic_integer_increase_by(VALUE self, VALUE num)\n+{\n+  semian_shm_object *ptr;\n+  TypedData_Get_Struct(self, semian_shm_object, &semian_shm_object_type, ptr);\n+\n+  if (0 == ptr->shm_address)\n+    return Qnil;\n+\n+  if (TYPE(num) != T_FIXNUM && TYPE(num) != T_FLOAT)\n+    return Qnil;\n+  semian_shm_object_check_and_resize_if_needed(self);\n+  if (!semian_shm_object_lock(self))\n+    return Qnil;\n+\n+  ((semian_atomic_int *)(ptr->shm_address))->value += NUM2INT(num);\n+\n+  semian_shm_object_unlock(self);\n+  return self;\n+}\n+\n+void\n+Init_semian_atomic_integer (void)\n+{\n+  // Bind methods to AtomicInteger\n+  VALUE cSemianModule = rb_const_get(rb_cObject, rb_intern(\"Semian\"));\n+  VALUE cAtomicInteger = rb_const_get(cSemianModule, rb_intern(\"AtomicInteger\"));\n+\n+  rb_define_method(cAtomicInteger, \"bind_init_fn\", semian_atomic_integer_bind_init_fn_wrapper, 0);\n+  rb_define_method(cAtomicInteger, \"value\", semian_atomic_integer_get_value, 0);\n+  rb_define_method(cAtomicInteger, \"value=\", semian_atomic_integer_set_value, 1);\n+  rb_define_method(cAtomicInteger, \"increase_by\", semian_atomic_integer_increase_by, 1);","path":"ext/semian/semian_atomic_integer.c","position":107,"original_position":107,"commit_id":"1f0960b1e11bf2ab84ecbf82e62e38372725d1b8","original_commit_id":"1f0960b1e11bf2ab84ecbf82e62e38372725d1b8","user":{"login":"Sirupsen","id":97400,"avatar_url":"https://avatars.githubusercontent.com/u/97400?v=3","gravatar_id":"","url":"https://api.github.com/users/Sirupsen","html_url":"https://github.com/Sirupsen","followers_url":"https://api.github.com/users/Sirupsen/followers","following_url":"https://api.github.com/users/Sirupsen/following{/other_user}","gists_url":"https://api.github.com/users/Sirupsen/gists{/gist_id}","starred_url":"https://api.github.com/users/Sirupsen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Sirupsen/subscriptions","organizations_url":"https://api.github.com/users/Sirupsen/orgs","repos_url":"https://api.github.com/users/Sirupsen/repos","events_url":"https://api.github.com/users/Sirupsen/events{/privacy}","received_events_url":"https://api.github.com/users/Sirupsen/received_events","type":"User","site_admin":false},"body":"Let me think about that when I've read all of the code :)","created_at":"2015-10-22T18:08:12Z","updated_at":"2015-10-22T18:08:12Z","html_url":"https://github.com/Shopify/semian/pull/54#discussion_r42783724","pull_request_url":"https://api.github.com/repos/Shopify/semian/pulls/54","_links":{"self":{"href":"https://api.github.com/repos/Shopify/semian/pulls/comments/42783724"},"html":{"href":"https://github.com/Shopify/semian/pull/54#discussion_r42783724"},"pull_request":{"href":"https://api.github.com/repos/Shopify/semian/pulls/54"}}},"pull_request":{"url":"https://api.github.com/repos/Shopify/semian/pulls/54","id":46209222,"html_url":"https://github.com/Shopify/semian/pull/54","diff_url":"https://github.com/Shopify/semian/pull/54.diff","patch_url":"https://github.com/Shopify/semian/pull/54.patch","issue_url":"https://api.github.com/repos/Shopify/semian/issues/54","number":54,"state":"open","locked":false,"title":"Initial commit implementing a per-host circuit breaker state using shared memory and semaphores","user":{"login":"kyewei","id":4326799,"avatar_url":"https://avatars.githubusercontent.com/u/4326799?v=3","gravatar_id":"","url":"https://api.github.com/users/kyewei","html_url":"https://github.com/kyewei","followers_url":"https://api.github.com/users/kyewei/followers","following_url":"https://api.github.com/users/kyewei/following{/other_user}","gists_url":"https://api.github.com/users/kyewei/gists{/gist_id}","starred_url":"https://api.github.com/users/kyewei/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kyewei/subscriptions","organizations_url":"https://api.github.com/users/kyewei/orgs","repos_url":"https://api.github.com/users/kyewei/repos","events_url":"https://api.github.com/users/kyewei/events{/privacy}","received_events_url":"https://api.github.com/users/kyewei/received_events","type":"User","site_admin":false},"body":"@Sirupsen @csfrancis @byroot for review, suggest refactoring if needed!\r\n\r\nImplementation details:\r\nShared memory stores the space of `int`+`int`+`double[]`, corresponding to `@successes`, `@errors.count`, and the `@errors` array itself (stored as doubles). The array has a max size of `@errors.count` since the Ruby code ensures the array never grows above that size.\r\n\r\n* Most of semaphore initialization, permissions, etc were borrowed from `semian.c`. \r\n* A regular array (as opposed to a linked list) was chosen because of the small array length and simplicity.\r\n* Shared memory is not explicitly marked for deletion from the C side since (1) it's on the order of ten's of bytes, (2) it won't increase if the same key/id is used to create the memory. This is the same(?) rationale as why in `semian.c`, `semian_resource_free()` doesn't clear the semaphores, instead leaving them to be cleared on reboot.\r\n\r\n****\r\n\r\nPR v2 (10/06/2015, up to hash 1f0960b...)\r\n\r\nI did quite a lot of refactoring, so here's a detailed overview. \r\n* The final structure looks like this: `Semian::SharedMemoryObject` has two subclasses, `Semian::AtomicInteger` and `Semian::SlidingWindow`. I took suggestions to abstract the memory management further so that `Semian::CircuitBreaker` would only be using instances of the specific subclasses. I also added header files so code can be shared.\r\n* `Semian::AtomicEnum` subclasses `Semian::AtomicInteger` to provide shared memory for `CircuitBreaker`'s `@state`. It runs like a traditional C-style enum type thats backed by integers. It's used in `@state`'s `:closed`, `:open`, `:half_open`\r\n* `SharedMemoryObject` provides and manages the semaphore, shared memory, and the subclasses implement the use cases for the memory. Constructor calls look like this: `SharedMemoryObject.new(\"name\", [:int, :int, :long, :long], 0660)` and specify the memory structure. The associated C structure has a reference to function of type `void (*object_init_fn)(size_t, void *, void *, size_t, int)` so a custom data initializer can be used for each type\r\n* I thought about using `extend Forwardable` for delegating and decorating but that would introduce some complications with binding C functions to objects (things like `AtomicInteger#value` would need an extra argument to access `SharedMemoryObject` functions)\r\n* I exposed a `SharedMemoryObject#execute_atomically` to allow custom actions such as those found in `CircuitBreaker#push_time` without providing `#lock` and `#unlock` explicitly\r\n* Dynamic resizing and initialization for `SlidingWindow` now works properly. The following scenarios resize: \r\n  * Worker 1 uses memory of `size=n`; worker 2 is created, requesting memory of `size!=n`; both workers will use new size, with data copied over and reinitialized using `object_init_fn()`. This occurs when you restart workers with new configurations\r\n  * `#resize_to` is called with new size on a worker, every worker will resize to new size\r\n* When initializing, memory is 0-initialized only if the worker is the only worker attached to it. Otherwise, memory resizing/copying is done. Use case: if one worker crashes, it can be restarted without losing data. However if all workers are restarted, it makes sense to reset the configuration to prevent some edge case from occurring.\r\n* Added tests for the shared memory. Looking for suggestions for more tests / or if there are better ways to do things than what I've done.\r\n* Fixed bugs/error locations\r\n\r\nI'm looking for suggestions for how to structure the fallback ruby implementation so that both the fallback and the shared memory versions of `AtomicInteger`, `SlidingWindow`, etc can coexist. Right now C code hooks and replaces them if semaphores are enabled. \r\n\r\nAlso, if I missed anything, comment below.","created_at":"2015-09-28T21:15:53Z","updated_at":"2015-10-22T18:08:12Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3de1485631848bd262e77dafd9f34ac3e7088869","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/Shopify/semian/pulls/54/commits","review_comments_url":"https://api.github.com/repos/Shopify/semian/pulls/54/comments","review_comment_url":"https://api.github.com/repos/Shopify/semian/pulls/comments{/number}","comments_url":"https://api.github.com/repos/Shopify/semian/issues/54/comments","statuses_url":"https://api.github.com/repos/Shopify/semian/statuses/1f0960b1e11bf2ab84ecbf82e62e38372725d1b8","head":{"label":"Shopify:circuit-breaker-per-host","ref":"circuit-breaker-per-host","sha":"1f0960b1e11bf2ab84ecbf82e62e38372725d1b8","user":{"login":"Shopify","id":8085,"avatar_url":"https://avatars.githubusercontent.com/u/8085?v=3","gravatar_id":"","url":"https://api.github.com/users/Shopify","html_url":"https://github.com/Shopify","followers_url":"https://api.github.com/users/Shopify/followers","following_url":"https://api.github.com/users/Shopify/following{/other_user}","gists_url":"https://api.github.com/users/Shopify/gists{/gist_id}","starred_url":"https://api.github.com/users/Shopify/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Shopify/subscriptions","organizations_url":"https://api.github.com/users/Shopify/orgs","repos_url":"https://api.github.com/users/Shopify/repos","events_url":"https://api.github.com/users/Shopify/events{/privacy}","received_events_url":"https://api.github.com/users/Shopify/received_events","type":"Organization","site_admin":false},"repo":{"id":24375548,"name":"semian","full_name":"Shopify/semian","owner":{"login":"Shopify","id":8085,"avatar_url":"https://avatars.githubusercontent.com/u/8085?v=3","gravatar_id":"","url":"https://api.github.com/users/Shopify","html_url":"https://github.com/Shopify","followers_url":"https://api.github.com/users/Shopify/followers","following_url":"https://api.github.com/users/Shopify/following{/other_user}","gists_url":"https://api.github.com/users/Shopify/gists{/gist_id}","starred_url":"https://api.github.com/users/Shopify/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Shopify/subscriptions","organizations_url":"https://api.github.com/users/Shopify/orgs","repos_url":"https://api.github.com/users/Shopify/repos","events_url":"https://api.github.com/users/Shopify/events{/privacy}","received_events_url":"https://api.github.com/users/Shopify/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Shopify/semian","description":":monkey: Resiliency toolkit for Ruby for failing fast","fork":false,"url":"https://api.github.com/repos/Shopify/semian","forks_url":"https://api.github.com/repos/Shopify/semian/forks","keys_url":"https://api.github.com/repos/Shopify/semian/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Shopify/semian/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Shopify/semian/teams","hooks_url":"https://api.github.com/repos/Shopify/semian/hooks","issue_events_url":"https://api.github.com/repos/Shopify/semian/issues/events{/number}","events_url":"https://api.github.com/repos/Shopify/semian/events","assignees_url":"https://api.github.com/repos/Shopify/semian/assignees{/user}","branches_url":"https://api.github.com/repos/Shopify/semian/branches{/branch}","tags_url":"https://api.github.com/repos/Shopify/semian/tags","blobs_url":"https://api.github.com/repos/Shopify/semian/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Shopify/semian/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Shopify/semian/git/refs{/sha}","trees_url":"https://api.github.com/repos/Shopify/semian/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Shopify/semian/statuses/{sha}","languages_url":"https://api.github.com/repos/Shopify/semian/languages","stargazers_url":"https://api.github.com/repos/Shopify/semian/stargazers","contributors_url":"https://api.github.com/repos/Shopify/semian/contributors","subscribers_url":"https://api.github.com/repos/Shopify/semian/subscribers","subscription_url":"https://api.github.com/repos/Shopify/semian/subscription","commits_url":"https://api.github.com/repos/Shopify/semian/commits{/sha}","git_commits_url":"https://api.github.com/repos/Shopify/semian/git/commits{/sha}","comments_url":"https://api.github.com/repos/Shopify/semian/comments{/number}","issue_comment_url":"https://api.github.com/repos/Shopify/semian/issues/comments{/number}","contents_url":"https://api.github.com/repos/Shopify/semian/contents/{+path}","compare_url":"https://api.github.com/repos/Shopify/semian/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Shopify/semian/merges","archive_url":"https://api.github.com/repos/Shopify/semian/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Shopify/semian/downloads","issues_url":"https://api.github.com/repos/Shopify/semian/issues{/number}","pulls_url":"https://api.github.com/repos/Shopify/semian/pulls{/number}","milestones_url":"https://api.github.com/repos/Shopify/semian/milestones{/number}","notifications_url":"https://api.github.com/repos/Shopify/semian/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Shopify/semian/labels{/name}","releases_url":"https://api.github.com/repos/Shopify/semian/releases{/id}","created_at":"2014-09-23T14:47:19Z","updated_at":"2015-10-21T14:44:06Z","pushed_at":"2015-10-06T18:14:25Z","git_url":"git://github.com/Shopify/semian.git","ssh_url":"git@github.com:Shopify/semian.git","clone_url":"https://github.com/Shopify/semian.git","svn_url":"https://github.com/Shopify/semian","homepage":"","size":888,"stargazers_count":163,"watchers_count":163,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":6,"mirror_url":null,"open_issues_count":12,"forks":6,"open_issues":12,"watchers":163,"default_branch":"master"}},"base":{"label":"Shopify:master","ref":"master","sha":"65ddd1a2e2e206bcf007cf1cb1a112eddc921d1a","user":{"login":"Shopify","id":8085,"avatar_url":"https://avatars.githubusercontent.com/u/8085?v=3","gravatar_id":"","url":"https://api.github.com/users/Shopify","html_url":"https://github.com/Shopify","followers_url":"https://api.github.com/users/Shopify/followers","following_url":"https://api.github.com/users/Shopify/following{/other_user}","gists_url":"https://api.github.com/users/Shopify/gists{/gist_id}","starred_url":"https://api.github.com/users/Shopify/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Shopify/subscriptions","organizations_url":"https://api.github.com/users/Shopify/orgs","repos_url":"https://api.github.com/users/Shopify/repos","events_url":"https://api.github.com/users/Shopify/events{/privacy}","received_events_url":"https://api.github.com/users/Shopify/received_events","type":"Organization","site_admin":false},"repo":{"id":24375548,"name":"semian","full_name":"Shopify/semian","owner":{"login":"Shopify","id":8085,"avatar_url":"https://avatars.githubusercontent.com/u/8085?v=3","gravatar_id":"","url":"https://api.github.com/users/Shopify","html_url":"https://github.com/Shopify","followers_url":"https://api.github.com/users/Shopify/followers","following_url":"https://api.github.com/users/Shopify/following{/other_user}","gists_url":"https://api.github.com/users/Shopify/gists{/gist_id}","starred_url":"https://api.github.com/users/Shopify/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Shopify/subscriptions","organizations_url":"https://api.github.com/users/Shopify/orgs","repos_url":"https://api.github.com/users/Shopify/repos","events_url":"https://api.github.com/users/Shopify/events{/privacy}","received_events_url":"https://api.github.com/users/Shopify/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/Shopify/semian","description":":monkey: Resiliency toolkit for Ruby for failing fast","fork":false,"url":"https://api.github.com/repos/Shopify/semian","forks_url":"https://api.github.com/repos/Shopify/semian/forks","keys_url":"https://api.github.com/repos/Shopify/semian/keys{/key_id}","collaborators_url":"https://api.github.com/repos/Shopify/semian/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/Shopify/semian/teams","hooks_url":"https://api.github.com/repos/Shopify/semian/hooks","issue_events_url":"https://api.github.com/repos/Shopify/semian/issues/events{/number}","events_url":"https://api.github.com/repos/Shopify/semian/events","assignees_url":"https://api.github.com/repos/Shopify/semian/assignees{/user}","branches_url":"https://api.github.com/repos/Shopify/semian/branches{/branch}","tags_url":"https://api.github.com/repos/Shopify/semian/tags","blobs_url":"https://api.github.com/repos/Shopify/semian/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/Shopify/semian/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/Shopify/semian/git/refs{/sha}","trees_url":"https://api.github.com/repos/Shopify/semian/git/trees{/sha}","statuses_url":"https://api.github.com/repos/Shopify/semian/statuses/{sha}","languages_url":"https://api.github.com/repos/Shopify/semian/languages","stargazers_url":"https://api.github.com/repos/Shopify/semian/stargazers","contributors_url":"https://api.github.com/repos/Shopify/semian/contributors","subscribers_url":"https://api.github.com/repos/Shopify/semian/subscribers","subscription_url":"https://api.github.com/repos/Shopify/semian/subscription","commits_url":"https://api.github.com/repos/Shopify/semian/commits{/sha}","git_commits_url":"https://api.github.com/repos/Shopify/semian/git/commits{/sha}","comments_url":"https://api.github.com/repos/Shopify/semian/comments{/number}","issue_comment_url":"https://api.github.com/repos/Shopify/semian/issues/comments{/number}","contents_url":"https://api.github.com/repos/Shopify/semian/contents/{+path}","compare_url":"https://api.github.com/repos/Shopify/semian/compare/{base}...{head}","merges_url":"https://api.github.com/repos/Shopify/semian/merges","archive_url":"https://api.github.com/repos/Shopify/semian/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/Shopify/semian/downloads","issues_url":"https://api.github.com/repos/Shopify/semian/issues{/number}","pulls_url":"https://api.github.com/repos/Shopify/semian/pulls{/number}","milestones_url":"https://api.github.com/repos/Shopify/semian/milestones{/number}","notifications_url":"https://api.github.com/repos/Shopify/semian/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/Shopify/semian/labels{/name}","releases_url":"https://api.github.com/repos/Shopify/semian/releases{/id}","created_at":"2014-09-23T14:47:19Z","updated_at":"2015-10-21T14:44:06Z","pushed_at":"2015-10-06T18:14:25Z","git_url":"git://github.com/Shopify/semian.git","ssh_url":"git@github.com:Shopify/semian.git","clone_url":"https://github.com/Shopify/semian.git","svn_url":"https://github.com/Shopify/semian","homepage":"","size":888,"stargazers_count":163,"watchers_count":163,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":6,"mirror_url":null,"open_issues_count":12,"forks":6,"open_issues":12,"watchers":163,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/Shopify/semian/pulls/54"},"html":{"href":"https://github.com/Shopify/semian/pull/54"},"issue":{"href":"https://api.github.com/repos/Shopify/semian/issues/54"},"comments":{"href":"https://api.github.com/repos/Shopify/semian/issues/54/comments"},"review_comments":{"href":"https://api.github.com/repos/Shopify/semian/pulls/54/comments"},"review_comment":{"href":"https://api.github.com/repos/Shopify/semian/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/Shopify/semian/pulls/54/commits"},"statuses":{"href":"https://api.github.com/repos/Shopify/semian/statuses/1f0960b1e11bf2ab84ecbf82e62e38372725d1b8"}}}}