GitHub PullRequestReviewCommentEvent at 2014-09-29 15:18:18
Repository: futuroscope
Actor: houndci
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/codegram/futuroscope/pulls/comments/18160841","id":18160841,"diff_hunk":"@@ -11,29 +8,46 @@ module Futuroscope\n       expect(pool.workers).to have(3).workers\n     end\n \n-    describe \"queue\" do\n+    describe \"push\" do\n       it \"enqueues a job and runs it\" do\n         pool = Pool.new\n-        future = double(:future)\n+        future = Struct.new(:worker_thread).new(nil)\n \n-        expect(future).to receive :run_future\n-        pool.queue future\n+        expect(future).to receive :resolve!\n+        pool.push future\n         sleep(0.1)\n       end\n     end\n \n+    describe \"depend\" do\n+      it \"detects cyclical dependencies\" do\n+        pool = Pool.new(2..2)\n+        f2 = nil\n+        f1 = Future.new(pool) { f2 = Future.new(pool) { f1.future_value }; f2.future_value }\n+\n+        expect { f1.future_value }.to raise_error Futuroscope::DeadlockError, /Cyclical dependency detected/\n+        expect { f2.future_value }.to raise_error Futuroscope::DeadlockError, /Cyclical dependency detected/\n+      end\n+\n+      it \"detects non-cyclical deadlocks (when the pool is full and all futures are waiting for another future)\" do\n+        pool = Pool.new(1..1)\n+        f = Future.new(pool) { Future.new(pool) { 1 } + 1 }\n+\n+        expect { f.future_value }.to raise_error Futuroscope::DeadlockError, /Pool size is too low/","path":"spec/futuroscope/pool_spec.rb","position":40,"original_position":40,"commit_id":"ed62b96637f0d0b94b17470214f86c3510148037","original_commit_id":"ed62b96637f0d0b94b17470214f86c3510148037","user":{"login":"houndci","id":6697940,"avatar_url":"https://avatars.githubusercontent.com/u/6697940?v=2","gravatar_id":"","url":"https://api.github.com/users/houndci","html_url":"https://github.com/houndci","followers_url":"https://api.github.com/users/houndci/followers","following_url":"https://api.github.com/users/houndci/following{/other_user}","gists_url":"https://api.github.com/users/houndci/gists{/gist_id}","starred_url":"https://api.github.com/users/houndci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/houndci/subscriptions","organizations_url":"https://api.github.com/users/houndci/orgs","repos_url":"https://api.github.com/users/houndci/repos","events_url":"https://api.github.com/users/houndci/events{/privacy}","received_events_url":"https://api.github.com/users/houndci/received_events","type":"User","site_admin":false},"body":"Line is too long. [99/80]","created_at":"2014-09-29T15:18:18Z","updated_at":"2014-09-29T15:18:18Z","html_url":"https://github.com/codegram/futuroscope/pull/23#discussion_r18160841","pull_request_url":"https://api.github.com/repos/codegram/futuroscope/pulls/23","_links":{"self":{"href":"https://api.github.com/repos/codegram/futuroscope/pulls/comments/18160841"},"html":{"href":"https://github.com/codegram/futuroscope/pull/23#discussion_r18160841"},"pull_request":{"href":"https://api.github.com/repos/codegram/futuroscope/pulls/23"}}},"pull_request":{"url":"https://api.github.com/repos/codegram/futuroscope/pulls/23","id":21940123,"html_url":"https://github.com/codegram/futuroscope/pull/23","diff_url":"https://github.com/codegram/futuroscope/pull/23.diff","patch_url":"https://github.com/codegram/futuroscope/pull/23.patch","issue_url":"https://api.github.com/repos/codegram/futuroscope/issues/23","number":23,"state":"open","locked":false,"title":"Huge feature pack: Deadlock detection, prioritized futures, smart up/down spinning of workers, logging","user":{"login":"ggPeti","id":3217744,"avatar_url":"https://avatars.githubusercontent.com/u/3217744?v=2","gravatar_id":"","url":"https://api.github.com/users/ggPeti","html_url":"https://github.com/ggPeti","followers_url":"https://api.github.com/users/ggPeti/followers","following_url":"https://api.github.com/users/ggPeti/following{/other_user}","gists_url":"https://api.github.com/users/ggPeti/gists{/gist_id}","starred_url":"https://api.github.com/users/ggPeti/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ggPeti/subscriptions","organizations_url":"https://api.github.com/users/ggPeti/orgs","repos_url":"https://api.github.com/users/ggPeti/repos","events_url":"https://api.github.com/users/ggPeti/events{/privacy}","received_events_url":"https://api.github.com/users/ggPeti/received_events","type":"User","site_admin":false},"body":"Apologies for the giant PR, but one thing led to another, and it didn't make sense to implement one without another (well, logging could have been separated, but it was very useful for debugging while implementing the rest).\r\n\r\nThe features implemented are:\r\n\r\n#### Deadlock detection\r\nThere are 2 kinds of deadlocks that we can detect.\r\n##### 1. Circular dependency\r\nWhen futures depend on each other in a circle, Futuroscope will detect that and send each thread involved a DeadlockError with a message describing the situation. For example:\r\n```\r\n2.1.0 :001 > f2 = nil\r\n => nil \r\n2.1.0 :002 > f1 = future { f2 = future { f1.future_value }; f2.future_value }; f1.inspect\r\nFuturoscope::DeadlockError: Cyclical dependency detected, the future was aborted.\r\n\t(...stack trace...)\r\n2.1.0 :004 > f2.inspect\r\nFuturoscope::DeadlockError: Cyclical dependency detected, the future was aborted.\r\n\t(...stack trace...)\r\n```\r\n##### 2. Pool size too low\r\nWhen the pool is full, but all futures are waiting for another future that doesn't have a worker yet, Futuroscope will fail a future which does not depend on any other future (and the one with the least priority out of those). (Note: This selection might be optimized to selecting the root of the smallest unary branch of the dependency forest instead of the root of the smallest tree, but this is a good enough solution for a situation that rarely occurs.)\r\nFor example:\r\n```\r\n2.1.0 :001 > Futuroscope.default_pool = Futuroscope::Pool.new 1..1\r\n => #<Futuroscope::Pool:0x000001020adef0 @min_workers=1, @max_workers=1, @dependencies={}, @priorities={}, @future_needs_worker=#<Thread::ConditionVariable:0x000001020ade78>, @workers=#<Set: {#<Futuroscope::Worker:0x000001020add88 @pool=#<Futuroscope::Pool:0x000001020adef0 ...>, @free=true, @thread=#<Thread:0x000001020add38 sleep>>}>, @mutex=#<Mutex:0x000001020addb0>, @futures={}> \r\n2.1.0 :002 > future { future { 1 } + 1 }\r\nFuturoscope::DeadlockError: Pool size is too low, the future was aborted.\r\n\t(...stack trace...)\r\n```\r\n\r\n#### Prioritized futures\r\nInstead of a queue, Futuroscope now uses something resembling a priority queue, where the priority is determined by how many threads are directly or indirectly blocking on the future's value. This can avoid deadlock situations, for example:\r\n```\r\n2.1.0 :001 > Futuroscope.default_pool = Futuroscope::Pool.new 2..2\r\n => #<Futuroscope::Pool:0x000001018c2470 @min_workers=2, @max_workers=2, @dependencies={}, @priorities={}, @future_needs_worker=#<Thread::ConditionVariable:0x000001018c23f8>, @workers=#<Set: {#<Futuroscope::Worker:0x000001018c2308 @pool=#<Futuroscope::Pool:0x000001018c2470 ...>, @free=true, @thread=#<Thread:0x000001018c22e0 sleep>>, #<Futuroscope::Worker:0x000001018c2150 @pool=#<Futuroscope::Pool:0x000001018c2470 ...>, @free=true, @thread=#<Thread:0x000001018c2128 sleep>>}>, @mutex=#<Mutex:0x000001018c2358>, @futures={}> \r\n2.1.0 :002 > f1, f2, f3, f4 = future { sleep 1; f4.future_value }, future { sleep 3 }, future { sleep 1; f4.future_value }, future { 4 }\r\n => [4, 3, 4, 4]\r\n```\r\nIn the old implementation, this would raise a fatal error, because `f1` starts to block on `f4` before it gets to the queue, then `f3` starts to block on `f4` as well after `f2` is done, so the pool gets full of blocking futures before `f4` has a chance to be evaluated.\r\n\r\n#### Smart up/down spinning of workers\r\nPreviously, with the push of every new future, there was a 50% chance that a new worker gets spun up if the limit hasn't been reached yet. Now the workers keep track whether they are free or busy, and the pool only spins up a new worker if it has more futures without workers than free workers.\r\n\r\nAlso previously every thread immediately quit if it was over the minimum thread count. Now, if the pool has more workers than the minimum, the workers have a 2 second linger period in which they will pick up new work if it's available, and only if no new work came in will they die. This is to minimize the cost of spinning threads up and down when the work comes in close spikes.\r\n\r\nThis also leads to better parallelization in some cases. Old version:\r\n```\r\n2.1.2 :001 > require 'benchmark'\r\n => true \r\n2.1.2 :002 > Futuroscope.default_pool = Futuroscope::Pool.new 1..10\r\n => #<Futuroscope::Pool:0x007fe502131840 @min_workers=1, @max_workers=10, @queue=#<Thread::Queue:0x007fe5021317f0>, @workers=#<Set: {#<Futuroscope::Worker:0x007fe5021316d8 @pool=#<Futuroscope::Pool:0x007fe502131840 ...>, @thread=#<Thread:0x007fe502131688 sleep>>}>, @mutex=#<Mutex:0x007fe502131700>> \r\n2.1.2 :004 > Benchmark.measure { (1..10).future_map { sleep 1 }.inspect }.real\r\n => 2.002055 \r\n```\r\nNew version:\r\n```\r\n2.1.0 :001 > require 'benchmark'\r\n => true \r\n2.1.0 :002 > Futuroscope.default_pool = Futuroscope::Pool.new 1..10\r\n => #<Futuroscope::Pool:0x0000010189afb0 @min_workers=1, @max_workers=10, @dependencies={}, @priorities={}, @future_needs_worker=#<Thread::ConditionVariable:0x0000010189af38>, @workers=#<Set: {#<Futuroscope::Worker:0x0000010189ae20 @pool=#<Futuroscope::Pool:0x0000010189afb0 ...>, @free=true, @thread=#<Thread:0x0000010189adf8 sleep>>}>, @mutex=#<Mutex:0x0000010189ae70>, @futures={}> \r\n2.1.0 :003 > Benchmark.measure { (1..10).future_map { sleep 1 }.inspect }.real\r\n => 1.002627 \r\n```\r\n\r\n#### Logging\r\nFuturoscope now supports logging. If you put loggers into `Futuroscope.loggers` (which is a simple array), they will get all log messages from inside. The loggers are expected to conform with Ruby's core `Logger`, in that they respond to these messages: `debug`, `info`, `warn`, `error`, `fatal`.\r\n\r\nExample:\r\n```\r\n2.1.0 :001 > require 'logger'\r\n => true \r\n2.1.0 :002 > Futuroscope.loggers << Logger.new(STDERR)\r\n => [#<Logger:0x00000102887f90 @progname=nil, @level=0, @default_formatter=#<Logger::Formatter:0x00000102887f68 @datetime_format=nil>, @formatter=nil, @logdev=#<Logger::LogDevice:0x00000102887f18 @shift_size=nil, @shift_age=nil, @filename=nil, @dev=#<IO:<STDERR>>, @mutex=#<Logger::LogDevice::LogDeviceMutex:0x00000102887ef0 @mon_owner=nil, @mon_count=0, @mon_mutex=#<Mutex:0x00000102887ea0>>>>] \r\n2.1.0 :003 > Futuroscope.default_pool = Futuroscope::Pool.new 1..1\r\nI, [2014-09-29T16:50:17.214206 #85319]  INFO -- :         spun up worker with thread 2168719860\r\n => #<Futuroscope::Pool:0x00000102881690 @min_workers=1, @max_workers=1, @dependencies={}, @priorities={}, @future_needs_worker=#<Thread::ConditionVariable:0x00000102881500>, @workers=#<Set: {#<Futuroscope::Worker:0x00000102881410 @pool=#<Futuroscope::Pool:0x00000102881690 ...>, @free=true, @thread=#<Thread:0x000001028813e8 sleep>>}>, @mutex=#<Mutex:0x00000102881438>, @futures={}> \r\nI, [2014-09-29T16:50:17.214845 #85319]  INFO -- : POP:    thread 2168719860 going to sleep until there's something to do...\r\n2.1.0 :004 > f = future { f.future_value }\r\nI, [2014-09-29T16:50:25.741545 #85319]  INFO -- : PUSH:   added future 2168704840\r\nI, [2014-09-29T16:50:25.741620 #85319]  INFO -- :         sending signal to wake up a thread\r\nD, [2014-09-29T16:50:25.741673 #85319] DEBUG -- :         current priorities: {\"future 2168704840\"=>0}\r\nI, [2014-09-29T16:50:25.741800 #85319]  INFO -- : DEPEND: thread 2156059640 depends on future 2168704840\r\nD, [2014-09-29T16:50:25.741861 #85319] DEBUG -- :         current dependencies: {\"thread 2156059640\"=>\"future 2168704840\"}\r\nI, [2014-09-29T16:50:25.741962 #85319]  INFO -- :         incrementing priority for future 2168704840\r\nI, [2014-09-29T16:50:25.742025 #85319]  INFO -- : POP:    ... thread 2168719860 woke up\r\nD, [2014-09-29T16:50:25.742069 #85319] DEBUG -- :         current priorities: {\"future 2168704840\"=>1}\r\nD, [2014-09-29T16:50:25.742110 #85319] DEBUG -- :         current future workers: {\"future 2168704840\"=>nil}\r\nI, [2014-09-29T16:50:25.742156 #85319]  INFO -- : POP:    thread 2168719860 will start working on future 2168704840\r\nI, [2014-09-29T16:50:25.742203 #85319]  INFO -- : DEPEND: thread 2168719860 depends on future 2168704840\r\nD, [2014-09-29T16:50:25.742266 #85319] DEBUG -- :         current dependencies: {\"thread 2156059640\"=>\"future 2168704840\", \"thread 2168719860\"=>\"future 2168704840\"}\r\nE, [2014-09-29T16:50:25.742313 #85319] ERROR -- :         deadlock! cyclical dependency, sending interrupt to all threads involved\r\nI, [2014-09-29T16:50:25.742388 #85319]  INFO -- : DONE:   thread 2168719860 is done with future 2168704840\r\nI, [2014-09-29T16:50:25.742423 #85319]  INFO -- :         deleting future 2168704840 from the task list\r\nI, [2014-09-29T16:50:25.742462 #85319]  INFO -- :         deleting dependency from thread 2156059640 to future 2168704840\r\nI, [2014-09-29T16:50:25.742498 #85319]  INFO -- :         deleting dependency from thread 2168719860 to future 2168704840\r\nI, [2014-09-29T16:50:25.742548 #85319]  INFO -- : POP:    thread 2168719860 going to sleep until there's something to do...\r\nFuturoscope::DeadlockError: Cyclical dependency detected, the future was aborted.\r\n\t(...stack trace...)\r\n```\r\n---\r\nI'd also like to note that this PR comes with multiple engineering decisions that are worth discussing. For example, I've moved away from using a `Queue` in both the pool and the future; instead, I'm now making use of `ConditionVariable`s (which `Queue` is using internally as well).\r\n\r\nAnother one is that the pool keeps track of the `__id__`s of the futures as hash keys. Why is that so, why am I using 2 hashes instead of one: one with `future.__id__`s pointing to priority values, and one with `future.__id__`s pointing to the actual futures? Because if you want to use a `Delegator` as a hash key, Ruby will call `#hash` on the object, which gets forwarded to the wrapped object, creating a deadlock. Not forwarding `#hash` is no solution: it will make the futures non-transparent, `hsh[:key]` will not be the same as `hsh[future { :key }]`.\r\n\r\nPlease discuss any parts that you disagree with, I'm happy to elaborate and of course it's always possible that I missed something.","created_at":"2014-09-29T14:58:40Z","updated_at":"2014-09-29T15:18:18Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7f0f88b885d286450053ad14710573bca9c24469","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/codegram/futuroscope/pulls/23/commits","review_comments_url":"https://api.github.com/repos/codegram/futuroscope/pulls/23/comments","review_comment_url":"https://api.github.com/repos/codegram/futuroscope/pulls/comments/{number}","comments_url":"https://api.github.com/repos/codegram/futuroscope/issues/23/comments","statuses_url":"https://api.github.com/repos/codegram/futuroscope/statuses/ed62b96637f0d0b94b17470214f86c3510148037","head":{"label":"ggPeti:deadlock_priorities_logging","ref":"deadlock_priorities_logging","sha":"ed62b96637f0d0b94b17470214f86c3510148037","user":{"login":"ggPeti","id":3217744,"avatar_url":"https://avatars.githubusercontent.com/u/3217744?v=2","gravatar_id":"","url":"https://api.github.com/users/ggPeti","html_url":"https://github.com/ggPeti","followers_url":"https://api.github.com/users/ggPeti/followers","following_url":"https://api.github.com/users/ggPeti/following{/other_user}","gists_url":"https://api.github.com/users/ggPeti/gists{/gist_id}","starred_url":"https://api.github.com/users/ggPeti/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ggPeti/subscriptions","organizations_url":"https://api.github.com/users/ggPeti/orgs","repos_url":"https://api.github.com/users/ggPeti/repos","events_url":"https://api.github.com/users/ggPeti/events{/privacy}","received_events_url":"https://api.github.com/users/ggPeti/received_events","type":"User","site_admin":false},"repo":{"id":16350562,"name":"futuroscope","full_name":"ggPeti/futuroscope","owner":{"login":"ggPeti","id":3217744,"avatar_url":"https://avatars.githubusercontent.com/u/3217744?v=2","gravatar_id":"","url":"https://api.github.com/users/ggPeti","html_url":"https://github.com/ggPeti","followers_url":"https://api.github.com/users/ggPeti/followers","following_url":"https://api.github.com/users/ggPeti/following{/other_user}","gists_url":"https://api.github.com/users/ggPeti/gists{/gist_id}","starred_url":"https://api.github.com/users/ggPeti/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ggPeti/subscriptions","organizations_url":"https://api.github.com/users/ggPeti/orgs","repos_url":"https://api.github.com/users/ggPeti/repos","events_url":"https://api.github.com/users/ggPeti/events{/privacy}","received_events_url":"https://api.github.com/users/ggPeti/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/ggPeti/futuroscope","description":"Yet another Futures implementation in Ruby","fork":true,"url":"https://api.github.com/repos/ggPeti/futuroscope","forks_url":"https://api.github.com/repos/ggPeti/futuroscope/forks","keys_url":"https://api.github.com/repos/ggPeti/futuroscope/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ggPeti/futuroscope/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ggPeti/futuroscope/teams","hooks_url":"https://api.github.com/repos/ggPeti/futuroscope/hooks","issue_events_url":"https://api.github.com/repos/ggPeti/futuroscope/issues/events{/number}","events_url":"https://api.github.com/repos/ggPeti/futuroscope/events","assignees_url":"https://api.github.com/repos/ggPeti/futuroscope/assignees{/user}","branches_url":"https://api.github.com/repos/ggPeti/futuroscope/branches{/branch}","tags_url":"https://api.github.com/repos/ggPeti/futuroscope/tags","blobs_url":"https://api.github.com/repos/ggPeti/futuroscope/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ggPeti/futuroscope/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ggPeti/futuroscope/git/refs{/sha}","trees_url":"https://api.github.com/repos/ggPeti/futuroscope/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ggPeti/futuroscope/statuses/{sha}","languages_url":"https://api.github.com/repos/ggPeti/futuroscope/languages","stargazers_url":"https://api.github.com/repos/ggPeti/futuroscope/stargazers","contributors_url":"https://api.github.com/repos/ggPeti/futuroscope/contributors","subscribers_url":"https://api.github.com/repos/ggPeti/futuroscope/subscribers","subscription_url":"https://api.github.com/repos/ggPeti/futuroscope/subscription","commits_url":"https://api.github.com/repos/ggPeti/futuroscope/commits{/sha}","git_commits_url":"https://api.github.com/repos/ggPeti/futuroscope/git/commits{/sha}","comments_url":"https://api.github.com/repos/ggPeti/futuroscope/comments{/number}","issue_comment_url":"https://api.github.com/repos/ggPeti/futuroscope/issues/comments/{number}","contents_url":"https://api.github.com/repos/ggPeti/futuroscope/contents/{+path}","compare_url":"https://api.github.com/repos/ggPeti/futuroscope/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ggPeti/futuroscope/merges","archive_url":"https://api.github.com/repos/ggPeti/futuroscope/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ggPeti/futuroscope/downloads","issues_url":"https://api.github.com/repos/ggPeti/futuroscope/issues{/number}","pulls_url":"https://api.github.com/repos/ggPeti/futuroscope/pulls{/number}","milestones_url":"https://api.github.com/repos/ggPeti/futuroscope/milestones{/number}","notifications_url":"https://api.github.com/repos/ggPeti/futuroscope/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ggPeti/futuroscope/labels{/name}","releases_url":"https://api.github.com/repos/ggPeti/futuroscope/releases{/id}","created_at":"2014-01-29T16:24:37Z","updated_at":"2014-08-03T20:12:17Z","pushed_at":"2014-09-29T15:18:09Z","git_url":"git://github.com/ggPeti/futuroscope.git","ssh_url":"git@github.com:ggPeti/futuroscope.git","clone_url":"https://github.com/ggPeti/futuroscope.git","svn_url":"https://github.com/ggPeti/futuroscope","homepage":null,"size":268,"stargazers_count":1,"watchers_count":1,"language":"Ruby","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"codegram:master","ref":"master","sha":"b744a1018a16cb17e343a79f0793f4d8b0e24d45","user":{"login":"codegram","id":82056,"avatar_url":"https://avatars.githubusercontent.com/u/82056?v=2","gravatar_id":"","url":"https://api.github.com/users/codegram","html_url":"https://github.com/codegram","followers_url":"https://api.github.com/users/codegram/followers","following_url":"https://api.github.com/users/codegram/following{/other_user}","gists_url":"https://api.github.com/users/codegram/gists{/gist_id}","starred_url":"https://api.github.com/users/codegram/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codegram/subscriptions","organizations_url":"https://api.github.com/users/codegram/orgs","repos_url":"https://api.github.com/users/codegram/repos","events_url":"https://api.github.com/users/codegram/events{/privacy}","received_events_url":"https://api.github.com/users/codegram/received_events","type":"Organization","site_admin":false},"repo":{"id":9817150,"name":"futuroscope","full_name":"codegram/futuroscope","owner":{"login":"codegram","id":82056,"avatar_url":"https://avatars.githubusercontent.com/u/82056?v=2","gravatar_id":"","url":"https://api.github.com/users/codegram","html_url":"https://github.com/codegram","followers_url":"https://api.github.com/users/codegram/followers","following_url":"https://api.github.com/users/codegram/following{/other_user}","gists_url":"https://api.github.com/users/codegram/gists{/gist_id}","starred_url":"https://api.github.com/users/codegram/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/codegram/subscriptions","organizations_url":"https://api.github.com/users/codegram/orgs","repos_url":"https://api.github.com/users/codegram/repos","events_url":"https://api.github.com/users/codegram/events{/privacy}","received_events_url":"https://api.github.com/users/codegram/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/codegram/futuroscope","description":"Yet another Futures implementation in Ruby","fork":false,"url":"https://api.github.com/repos/codegram/futuroscope","forks_url":"https://api.github.com/repos/codegram/futuroscope/forks","keys_url":"https://api.github.com/repos/codegram/futuroscope/keys{/key_id}","collaborators_url":"https://api.github.com/repos/codegram/futuroscope/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/codegram/futuroscope/teams","hooks_url":"https://api.github.com/repos/codegram/futuroscope/hooks","issue_events_url":"https://api.github.com/repos/codegram/futuroscope/issues/events{/number}","events_url":"https://api.github.com/repos/codegram/futuroscope/events","assignees_url":"https://api.github.com/repos/codegram/futuroscope/assignees{/user}","branches_url":"https://api.github.com/repos/codegram/futuroscope/branches{/branch}","tags_url":"https://api.github.com/repos/codegram/futuroscope/tags","blobs_url":"https://api.github.com/repos/codegram/futuroscope/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/codegram/futuroscope/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/codegram/futuroscope/git/refs{/sha}","trees_url":"https://api.github.com/repos/codegram/futuroscope/git/trees{/sha}","statuses_url":"https://api.github.com/repos/codegram/futuroscope/statuses/{sha}","languages_url":"https://api.github.com/repos/codegram/futuroscope/languages","stargazers_url":"https://api.github.com/repos/codegram/futuroscope/stargazers","contributors_url":"https://api.github.com/repos/codegram/futuroscope/contributors","subscribers_url":"https://api.github.com/repos/codegram/futuroscope/subscribers","subscription_url":"https://api.github.com/repos/codegram/futuroscope/subscription","commits_url":"https://api.github.com/repos/codegram/futuroscope/commits{/sha}","git_commits_url":"https://api.github.com/repos/codegram/futuroscope/git/commits{/sha}","comments_url":"https://api.github.com/repos/codegram/futuroscope/comments{/number}","issue_comment_url":"https://api.github.com/repos/codegram/futuroscope/issues/comments/{number}","contents_url":"https://api.github.com/repos/codegram/futuroscope/contents/{+path}","compare_url":"https://api.github.com/repos/codegram/futuroscope/compare/{base}...{head}","merges_url":"https://api.github.com/repos/codegram/futuroscope/merges","archive_url":"https://api.github.com/repos/codegram/futuroscope/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/codegram/futuroscope/downloads","issues_url":"https://api.github.com/repos/codegram/futuroscope/issues{/number}","pulls_url":"https://api.github.com/repos/codegram/futuroscope/pulls{/number}","milestones_url":"https://api.github.com/repos/codegram/futuroscope/milestones{/number}","notifications_url":"https://api.github.com/repos/codegram/futuroscope/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/codegram/futuroscope/labels{/name}","releases_url":"https://api.github.com/repos/codegram/futuroscope/releases{/id}","created_at":"2013-05-02T16:23:19Z","updated_at":"2014-09-29T11:25:32Z","pushed_at":"2014-08-04T07:02:09Z","git_url":"git://github.com/codegram/futuroscope.git","ssh_url":"git@github.com:codegram/futuroscope.git","clone_url":"https://github.com/codegram/futuroscope.git","svn_url":"https://github.com/codegram/futuroscope","homepage":null,"size":784,"stargazers_count":191,"watchers_count":191,"language":"Ruby","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":11,"mirror_url":null,"open_issues_count":3,"forks":11,"open_issues":3,"watchers":191,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/codegram/futuroscope/pulls/23"},"html":{"href":"https://github.com/codegram/futuroscope/pull/23"},"issue":{"href":"https://api.github.com/repos/codegram/futuroscope/issues/23"},"comments":{"href":"https://api.github.com/repos/codegram/futuroscope/issues/23/comments"},"review_comments":{"href":"https://api.github.com/repos/codegram/futuroscope/pulls/23/comments"},"review_comment":{"href":"https://api.github.com/repos/codegram/futuroscope/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/codegram/futuroscope/pulls/23/commits"},"statuses":{"href":"https://api.github.com/repos/codegram/futuroscope/statuses/ed62b96637f0d0b94b17470214f86c3510148037"}}}}