{"action":"created","comment":{"url":"https://api.github.com/repos/project-rig/rig/pulls/comments/30302430","id":30302430,"diff_hunk":"@@ -79,65 +125,167 @@ def send_scp(self, buffer_size, x, y, p, cmd, arg1=0, arg2=0, arg3=0,\n             The packet that was received in acknowledgement of the transmitted\n             packet.\n         \"\"\"\n-        self.sock.settimeout(self.default_timeout + timeout)\n-\n-        # Construct the packet that will be sent\n-        packet = packets.SCPPacket(\n-            reply_expected=True, tag=0xff, dest_port=0, dest_cpu=p,\n-            src_port=7, src_cpu=31, dest_x=x, dest_y=y, src_x=0, src_y=0,\n-            cmd_rc=cmd, seq=self._seq, arg1=arg1, arg2=arg2, arg3=arg3,\n-            data=data\n-        )\n+        # This is implemented as a single burst packet sent using the bursty\n+        # interface.  This significantly reduces code duplication.\n+        # Construct a callable to retain the returned packet for us\n+        class Callback(object):\n+            def __init__(self):\n+                self.packet = None\n+\n+            def __call__(self, packet):\n+                self.packet = SCPPacket.from_bytestring(\n+                    packet, n_args=expected_args\n+                )\n \n-        # Determine how many bytes to listen to on the socket, this should\n-        # be the smallest power of two greater than the required size (for\n-        # efficiency reasons).\n-        max_length = buffer_size + consts.SDP_HEADER_LENGTH\n-        receive_length = 1 << 9  # 512 bytes is a reasonable minimum\n-        while receive_length < max_length:\n-            receive_length <<= 1\n+        # Create the packet to send\n+        callback = Callback()\n+        packets = [\n+            scpcall(x, y, p, cmd, arg1, arg2, arg3, data, expected_args,\n+                    callback, timeout)\n+        ]\n \n-        # Repeat until a reply is received or we run out of tries.\n-        n_tries = 0\n-        while n_tries < self.n_tries:\n-            # Transit the packet\n-            self.sock.send(packet.bytestring)\n-            n_tries += 1\n+        # Send the burst\n+        self.send_scp_burst(buffer_size, 1, iter(packets))\n \n-            try:\n-                # Try to receive the returned acknowledgement\n-                ack = self.sock.recv(receive_length)\n-            except IOError:\n-                # There was nothing to receive from the socket\n-                continue\n-\n-            # Convert the possible returned packet into an SCP packet. If\n-            # the sequence number matches the expected sequence number then\n-            # the acknowledgement has been received.\n-            scp = packets.SCPPacket.from_bytestring(ack, n_args=expected_args)\n-\n-            # Check that the CMD_RC isn't an error\n-            if scp.cmd_rc in self.error_codes:\n-                raise self.error_codes[scp.cmd_rc](\n-                    \"Packet with arguments: cmd={}, arg1={}, arg2={}, \"\n-                    \"arg3={}; sent to core ({},{},{}) was bad.\".format(\n-                        cmd, arg1, arg2, arg3, x, y, p\n+        # Return the received packet\n+        assert callback.packet is not None\n+        return callback.packet\n+\n+    def send_scp_burst(self, buffer_size, window_size,\n+                       parameters_and_callbacks):\n+        \"\"\"Send a burst of SCP packets and call a callback for each returned\n+        packet.\n+\n+        Parameters\n+        ----------\n+        buffer_size : int\n+            Number of bytes held in an SCP buffer by SARK, determines how many\n+            bytes will be expected in a socket.\n+        window_size : int\n+            Number of packets which can be awaiting replies from the SpiNNaker\n+            board.\n+        parameters_and_callbacks: iterable of :py:class:`.scpcall`\n+            Iterable of :py:class:`.scpcall` elements.  These elements can\n+            specify a callback which will be called with the returned packet.\n+        \"\"\"\n+        # Non-blocking and then the following is a busy loop.\n+        self.sock.setblocking(False)\n+\n+        # Calculate the receive length, this should be the smallest power of\n+        # two greater than the required size\n+        max_length = buffer_size + consts.SDP_HEADER_LENGTH\n+        receive_length = int(2**math.ceil(math.log(max_length, 2)))\n+\n+        class TransmittedPacket(object):\n+            \"\"\"A packet which has been transmitted and still awaits a response.\n+            \"\"\"\n+            __slots__ = [\"callback\", \"packet\", \"expected_args\", \"n_tries\",\n+                         \"time_sent\", \"extra_timeout\"]\n+\n+            def __init__(self, callback, packet, expected_args, extra_timeout):\n+                self.callback = callback\n+                self.packet = packet\n+                self.expected_args = expected_args\n+                self.extra_timeout = extra_timeout\n+                self.n_tries = 1\n+                self.time_sent = time.time()\n+\n+        queued_packets = True\n+        outstanding_packets = {}\n+\n+        # While there are packets in the queue or packets for which we are\n+        # still awaiting returns then continue to loop.\n+        while queued_packets or outstanding_packets:\n+            # If there are fewer outstanding packets than the window can take\n+            # and we still might have packets left to send then transmit a\n+            # packet and add it to the list of outstanding packets.\n+            if len(outstanding_packets) < window_size and queued_packets:\n+                try:\n+                    args = next(parameters_and_callbacks)\n+                except StopIteration:\n+                    queued_packets = False\n+\n+                if queued_packets:\n+                    # If we extracted a new packet to send then create a new\n+                    # outstanding packet and transmit it.\n+                    seq = next(self.seq)\n+                    while seq in outstanding_packets:\n+                        # The seq should rarely be already taken, it normally\n+                        # means that one packet is taking such a long time to\n+                        # send that the sequence has wrapped around.  It's not\n+                        # a problem provided that we don't reuse the number.\n+                        seq = next(self.seq)\n+\n+                    # Construct the packet that we'll be sending\n+                    packet = SCPPacket(\n+                        reply_expected=True, tag=0xff, dest_port=0,\n+                        dest_cpu=args.p, src_port=7, src_cpu=31,\n+                        dest_x=args.x, dest_y=args.y, src_x=0, src_y=0,\n+                        cmd_rc=args.cmd, seq=seq,\n+                        arg1=args.arg1, arg2=args.arg2, arg3=args.arg3,\n+                        data=args.data\n                     )\n-                )\n \n-            if scp.seq == self._seq:\n-                # The packet is the acknowledgement.  Increment the\n-                # sequence indicator and return the packet.\n-                self._seq ^= 1\n-                return scp\n+                    # Create a reference to this packet so that we know we're\n+                    # expecting a response for it and can retransmit it if\n+                    # necessary.\n+                    outstanding_packets[seq] = TransmittedPacket(\n+                        args.callback, packet.bytestring, args.expected_args,\n+                        args.timeout\n+                    )\n \n-        # The packet we transmitted wasn't acknowledged.\n-        raise TimeoutError(\n-            \"Exceeded {} tries when trying to transmit packet.\".format(\n-                self.n_tries)\n-        )\n+                    # Actually send the packet\n+                    self.sock.send(packet.bytestring)\n \n-    def read(self, buffer_size, x, y, p, address, length_bytes):\n+            # Listen on the socket for an acknowledgement packet, there not be\n+            # one.\n+            try:\n+                ack = self.sock.recv(receive_length)\n+            except IOError:\n+                # There wasn't a returned packet, we may spend quite some time\n+                # here.\n+                ack = None\n+\n+            # Process the received packet (if there is one)\n+            if ack is not None:\n+                # Extract the sequence number from the bytestring, iff possible\n+                rc, seq = struct.unpack_from(\"<2H\", ack,","path":"rig/machine_control/scp_connection.py","position":246,"original_position":246,"commit_id":"f25c795fc38fc14d4f0beeed38577d8873ae8c3f","original_commit_id":"f25c795fc38fc14d4f0beeed38577d8873ae8c3f","user":{"login":"mundya","id":3439837,"avatar_url":"https://avatars.githubusercontent.com/u/3439837?v=3","gravatar_id":"","url":"https://api.github.com/users/mundya","html_url":"https://github.com/mundya","followers_url":"https://api.github.com/users/mundya/followers","following_url":"https://api.github.com/users/mundya/following{/other_user}","gists_url":"https://api.github.com/users/mundya/gists{/gist_id}","starred_url":"https://api.github.com/users/mundya/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mundya/subscriptions","organizations_url":"https://api.github.com/users/mundya/orgs","repos_url":"https://api.github.com/users/mundya/repos","events_url":"https://api.github.com/users/mundya/events{/privacy}","received_events_url":"https://api.github.com/users/mundya/received_events","type":"User","site_admin":false},"body":"Yes. Why? The constant is the offset.","created_at":"2015-05-14T07:22:30Z","updated_at":"2015-05-14T07:22:30Z","html_url":"https://github.com/project-rig/rig/pull/117#discussion_r30302430","pull_request_url":"https://api.github.com/repos/project-rig/rig/pulls/117","_links":{"self":{"href":"https://api.github.com/repos/project-rig/rig/pulls/comments/30302430"},"html":{"href":"https://github.com/project-rig/rig/pull/117#discussion_r30302430"},"pull_request":{"href":"https://api.github.com/repos/project-rig/rig/pulls/117"}}},"pull_request":{"url":"https://api.github.com/repos/project-rig/rig/pulls/117","id":34155901,"html_url":"https://github.com/project-rig/rig/pull/117","diff_url":"https://github.com/project-rig/rig/pull/117.diff","patch_url":"https://github.com/project-rig/rig/pull/117.patch","issue_url":"https://api.github.com/repos/project-rig/rig/issues/117","number":117,"state":"open","locked":false,"title":"Windowed SCP for reads and writes","user":{"login":"mundya","id":3439837,"avatar_url":"https://avatars.githubusercontent.com/u/3439837?v=3","gravatar_id":"","url":"https://api.github.com/users/mundya","html_url":"https://github.com/mundya","followers_url":"https://api.github.com/users/mundya/followers","following_url":"https://api.github.com/users/mundya/following{/other_user}","gists_url":"https://api.github.com/users/mundya/gists{/gist_id}","starred_url":"https://api.github.com/users/mundya/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mundya/subscriptions","organizations_url":"https://api.github.com/users/mundya/orgs","repos_url":"https://api.github.com/users/mundya/repos","events_url":"https://api.github.com/users/mundya/events{/privacy}","received_events_url":"https://api.github.com/users/mundya/received_events","type":"User","site_admin":false},"body":"**Replaces #115**\r\n\r\nUses sliding window protocol to increase speed for bursts of SCP messages.\r\n\r\nTalking to a locally connection SpiNN3 I'm hitting ~20+ Mb/s for both writes and reads to (chip 0, 0) [with the window size set to 8, not 5 as it is in the code on GitHub]. (I've recorded 22Mbit/s and 23Mbit/s on multiple occasions :grinning:)\r\n\r\n - [x] Neaten documentation\r\n - [x] Rework `send_scp` to send a burst of one message (mostly to\r\n       reduce duplication of code)\r\n - [x] Fill in a few gaps in the tests.\r\n - [x] Set the default window size to 1\r\n - [ ] Correctly query the window size from SC&MP\r\n - [ ] Set the transient IPTag timeout correctly\r\n\r\n__Rebase to `master` before completing the PR!__","created_at":"2015-04-27T10:22:39Z","updated_at":"2015-05-14T07:22:30Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9d7cc9f36dc8233d35d27af4bf1a58882814081b","assignee":null,"milestone":{"url":"https://api.github.com/repos/project-rig/rig/milestones/2","html_url":"https://github.com/project-rig/rig/milestones/SpiNNaker%20Tools%201.4","labels_url":"https://api.github.com/repos/project-rig/rig/milestones/2/labels","id":1088864,"number":2,"title":"SpiNNaker Tools 1.4","description":"Implement support for the next version of SpiNNaker tools.\r\n\r\nThis will either require a major version number bump, or will nicely coincide with fixing the API and releasing 1.0.","creator":{"login":"mundya","id":3439837,"avatar_url":"https://avatars.githubusercontent.com/u/3439837?v=3","gravatar_id":"","url":"https://api.github.com/users/mundya","html_url":"https://github.com/mundya","followers_url":"https://api.github.com/users/mundya/followers","following_url":"https://api.github.com/users/mundya/following{/other_user}","gists_url":"https://api.github.com/users/mundya/gists{/gist_id}","starred_url":"https://api.github.com/users/mundya/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mundya/subscriptions","organizations_url":"https://api.github.com/users/mundya/orgs","repos_url":"https://api.github.com/users/mundya/repos","events_url":"https://api.github.com/users/mundya/events{/privacy}","received_events_url":"https://api.github.com/users/mundya/received_events","type":"User","site_admin":false},"open_issues":2,"closed_issues":0,"state":"open","created_at":"2015-04-29T08:38:43Z","updated_at":"2015-04-29T08:39:02Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/project-rig/rig/pulls/117/commits","review_comments_url":"https://api.github.com/repos/project-rig/rig/pulls/117/comments","review_comment_url":"https://api.github.com/repos/project-rig/rig/pulls/comments{/number}","comments_url":"https://api.github.com/repos/project-rig/rig/issues/117/comments","statuses_url":"https://api.github.com/repos/project-rig/rig/statuses/f25c795fc38fc14d4f0beeed38577d8873ae8c3f","head":{"label":"project-rig:even-faster-scp-rw2","ref":"even-faster-scp-rw2","sha":"f25c795fc38fc14d4f0beeed38577d8873ae8c3f","user":{"login":"project-rig","id":11227885,"avatar_url":"https://avatars.githubusercontent.com/u/11227885?v=3","gravatar_id":"","url":"https://api.github.com/users/project-rig","html_url":"https://github.com/project-rig","followers_url":"https://api.github.com/users/project-rig/followers","following_url":"https://api.github.com/users/project-rig/following{/other_user}","gists_url":"https://api.github.com/users/project-rig/gists{/gist_id}","starred_url":"https://api.github.com/users/project-rig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/project-rig/subscriptions","organizations_url":"https://api.github.com/users/project-rig/orgs","repos_url":"https://api.github.com/users/project-rig/repos","events_url":"https://api.github.com/users/project-rig/events{/privacy}","received_events_url":"https://api.github.com/users/project-rig/received_events","type":"Organization","site_admin":false},"repo":{"id":29497314,"name":"rig","full_name":"project-rig/rig","owner":{"login":"project-rig","id":11227885,"avatar_url":"https://avatars.githubusercontent.com/u/11227885?v=3","gravatar_id":"","url":"https://api.github.com/users/project-rig","html_url":"https://github.com/project-rig","followers_url":"https://api.github.com/users/project-rig/followers","following_url":"https://api.github.com/users/project-rig/following{/other_user}","gists_url":"https://api.github.com/users/project-rig/gists{/gist_id}","starred_url":"https://api.github.com/users/project-rig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/project-rig/subscriptions","organizations_url":"https://api.github.com/users/project-rig/orgs","repos_url":"https://api.github.com/users/project-rig/repos","events_url":"https://api.github.com/users/project-rig/events{/privacy}","received_events_url":"https://api.github.com/users/project-rig/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/project-rig/rig","description":"A collection of tools for developing SpiNNaker applications.","fork":false,"url":"https://api.github.com/repos/project-rig/rig","forks_url":"https://api.github.com/repos/project-rig/rig/forks","keys_url":"https://api.github.com/repos/project-rig/rig/keys{/key_id}","collaborators_url":"https://api.github.com/repos/project-rig/rig/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/project-rig/rig/teams","hooks_url":"https://api.github.com/repos/project-rig/rig/hooks","issue_events_url":"https://api.github.com/repos/project-rig/rig/issues/events{/number}","events_url":"https://api.github.com/repos/project-rig/rig/events","assignees_url":"https://api.github.com/repos/project-rig/rig/assignees{/user}","branches_url":"https://api.github.com/repos/project-rig/rig/branches{/branch}","tags_url":"https://api.github.com/repos/project-rig/rig/tags","blobs_url":"https://api.github.com/repos/project-rig/rig/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/project-rig/rig/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/project-rig/rig/git/refs{/sha}","trees_url":"https://api.github.com/repos/project-rig/rig/git/trees{/sha}","statuses_url":"https://api.github.com/repos/project-rig/rig/statuses/{sha}","languages_url":"https://api.github.com/repos/project-rig/rig/languages","stargazers_url":"https://api.github.com/repos/project-rig/rig/stargazers","contributors_url":"https://api.github.com/repos/project-rig/rig/contributors","subscribers_url":"https://api.github.com/repos/project-rig/rig/subscribers","subscription_url":"https://api.github.com/repos/project-rig/rig/subscription","commits_url":"https://api.github.com/repos/project-rig/rig/commits{/sha}","git_commits_url":"https://api.github.com/repos/project-rig/rig/git/commits{/sha}","comments_url":"https://api.github.com/repos/project-rig/rig/comments{/number}","issue_comment_url":"https://api.github.com/repos/project-rig/rig/issues/comments{/number}","contents_url":"https://api.github.com/repos/project-rig/rig/contents/{+path}","compare_url":"https://api.github.com/repos/project-rig/rig/compare/{base}...{head}","merges_url":"https://api.github.com/repos/project-rig/rig/merges","archive_url":"https://api.github.com/repos/project-rig/rig/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/project-rig/rig/downloads","issues_url":"https://api.github.com/repos/project-rig/rig/issues{/number}","pulls_url":"https://api.github.com/repos/project-rig/rig/pulls{/number}","milestones_url":"https://api.github.com/repos/project-rig/rig/milestones{/number}","notifications_url":"https://api.github.com/repos/project-rig/rig/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/project-rig/rig/labels{/name}","releases_url":"https://api.github.com/repos/project-rig/rig/releases{/id}","created_at":"2015-01-19T21:20:40Z","updated_at":"2015-05-03T15:04:13Z","pushed_at":"2015-05-14T06:21:59Z","git_url":"git://github.com/project-rig/rig.git","ssh_url":"git@github.com:project-rig/rig.git","clone_url":"https://github.com/project-rig/rig.git","svn_url":"https://github.com/project-rig/rig","homepage":"","size":3918,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":8,"forks":1,"open_issues":8,"watchers":0,"default_branch":"master"}},"base":{"label":"project-rig:master","ref":"master","sha":"53facec8f2b3c2ef34ee00497d954f1ed57d0380","user":{"login":"project-rig","id":11227885,"avatar_url":"https://avatars.githubusercontent.com/u/11227885?v=3","gravatar_id":"","url":"https://api.github.com/users/project-rig","html_url":"https://github.com/project-rig","followers_url":"https://api.github.com/users/project-rig/followers","following_url":"https://api.github.com/users/project-rig/following{/other_user}","gists_url":"https://api.github.com/users/project-rig/gists{/gist_id}","starred_url":"https://api.github.com/users/project-rig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/project-rig/subscriptions","organizations_url":"https://api.github.com/users/project-rig/orgs","repos_url":"https://api.github.com/users/project-rig/repos","events_url":"https://api.github.com/users/project-rig/events{/privacy}","received_events_url":"https://api.github.com/users/project-rig/received_events","type":"Organization","site_admin":false},"repo":{"id":29497314,"name":"rig","full_name":"project-rig/rig","owner":{"login":"project-rig","id":11227885,"avatar_url":"https://avatars.githubusercontent.com/u/11227885?v=3","gravatar_id":"","url":"https://api.github.com/users/project-rig","html_url":"https://github.com/project-rig","followers_url":"https://api.github.com/users/project-rig/followers","following_url":"https://api.github.com/users/project-rig/following{/other_user}","gists_url":"https://api.github.com/users/project-rig/gists{/gist_id}","starred_url":"https://api.github.com/users/project-rig/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/project-rig/subscriptions","organizations_url":"https://api.github.com/users/project-rig/orgs","repos_url":"https://api.github.com/users/project-rig/repos","events_url":"https://api.github.com/users/project-rig/events{/privacy}","received_events_url":"https://api.github.com/users/project-rig/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/project-rig/rig","description":"A collection of tools for developing SpiNNaker applications.","fork":false,"url":"https://api.github.com/repos/project-rig/rig","forks_url":"https://api.github.com/repos/project-rig/rig/forks","keys_url":"https://api.github.com/repos/project-rig/rig/keys{/key_id}","collaborators_url":"https://api.github.com/repos/project-rig/rig/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/project-rig/rig/teams","hooks_url":"https://api.github.com/repos/project-rig/rig/hooks","issue_events_url":"https://api.github.com/repos/project-rig/rig/issues/events{/number}","events_url":"https://api.github.com/repos/project-rig/rig/events","assignees_url":"https://api.github.com/repos/project-rig/rig/assignees{/user}","branches_url":"https://api.github.com/repos/project-rig/rig/branches{/branch}","tags_url":"https://api.github.com/repos/project-rig/rig/tags","blobs_url":"https://api.github.com/repos/project-rig/rig/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/project-rig/rig/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/project-rig/rig/git/refs{/sha}","trees_url":"https://api.github.com/repos/project-rig/rig/git/trees{/sha}","statuses_url":"https://api.github.com/repos/project-rig/rig/statuses/{sha}","languages_url":"https://api.github.com/repos/project-rig/rig/languages","stargazers_url":"https://api.github.com/repos/project-rig/rig/stargazers","contributors_url":"https://api.github.com/repos/project-rig/rig/contributors","subscribers_url":"https://api.github.com/repos/project-rig/rig/subscribers","subscription_url":"https://api.github.com/repos/project-rig/rig/subscription","commits_url":"https://api.github.com/repos/project-rig/rig/commits{/sha}","git_commits_url":"https://api.github.com/repos/project-rig/rig/git/commits{/sha}","comments_url":"https://api.github.com/repos/project-rig/rig/comments{/number}","issue_comment_url":"https://api.github.com/repos/project-rig/rig/issues/comments{/number}","contents_url":"https://api.github.com/repos/project-rig/rig/contents/{+path}","compare_url":"https://api.github.com/repos/project-rig/rig/compare/{base}...{head}","merges_url":"https://api.github.com/repos/project-rig/rig/merges","archive_url":"https://api.github.com/repos/project-rig/rig/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/project-rig/rig/downloads","issues_url":"https://api.github.com/repos/project-rig/rig/issues{/number}","pulls_url":"https://api.github.com/repos/project-rig/rig/pulls{/number}","milestones_url":"https://api.github.com/repos/project-rig/rig/milestones{/number}","notifications_url":"https://api.github.com/repos/project-rig/rig/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/project-rig/rig/labels{/name}","releases_url":"https://api.github.com/repos/project-rig/rig/releases{/id}","created_at":"2015-01-19T21:20:40Z","updated_at":"2015-05-03T15:04:13Z","pushed_at":"2015-05-14T06:21:59Z","git_url":"git://github.com/project-rig/rig.git","ssh_url":"git@github.com:project-rig/rig.git","clone_url":"https://github.com/project-rig/rig.git","svn_url":"https://github.com/project-rig/rig","homepage":"","size":3918,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":1,"mirror_url":null,"open_issues_count":8,"forks":1,"open_issues":8,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/project-rig/rig/pulls/117"},"html":{"href":"https://github.com/project-rig/rig/pull/117"},"issue":{"href":"https://api.github.com/repos/project-rig/rig/issues/117"},"comments":{"href":"https://api.github.com/repos/project-rig/rig/issues/117/comments"},"review_comments":{"href":"https://api.github.com/repos/project-rig/rig/pulls/117/comments"},"review_comment":{"href":"https://api.github.com/repos/project-rig/rig/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/project-rig/rig/pulls/117/commits"},"statuses":{"href":"https://api.github.com/repos/project-rig/rig/statuses/f25c795fc38fc14d4f0beeed38577d8873ae8c3f"}}}}