예제 #1
0
    def responded(self, req_id):
        self.clear_timeout(req_id)
        self.req_ids.discard(req_id)
        workers_by_id.pop(req_id, None)
        self.start_time.pop(req_id, None)
        if (self.cache_key is None or cached_workers.get(
                self.cache_key,
            (None, None))[1] is not self) and len(self.req_ids) == 0:
            # Drain complete; goodbye!
            if self.cache_key is not None:
                print("Cache worker for %s drained (version %s)" %
                      (self.cache_key, self.app_version))
            self.proc.terminate()

        maybe_quit_if_draining_and_done()
예제 #2
0
 def outbound_done():
     self.outbound_ids.pop(id, None)
     workers_by_id.pop(id, None)
     maybe_quit_if_draining_and_done()
예제 #3
0
    def read_loop(self):
        try:
            while True:
                # marshal.load() takes the GIL, so only do it once we know there's something there to load.
                dummy_char = self.proc.stdout.read(1)
                if len(dummy_char) == 0:
                    break
                msg = marshal.load(self.proc.stdout)
                type = msg.get("type")
                id = msg.get("id") or msg.get("requestId")

                if type == "CALL" or type == "GET_APP":
                    self.outbound_ids[msg["id"]] = msg.get('originating-call')
                    workers_by_id[msg["id"]] = self
                else:
                    if id is None:
                        if "output" in msg:
                            # Output from unknown thread? Broadcast it.
                            print(
                                "Broadcasting output from unknown thread: %s" %
                                msg)
                            for i in self.req_ids:
                                msg["id"] = i
                                send_with_header(msg)
                        else:
                            print("Discarding invalid message with no ID: %s" %
                                  repr(msg))
                        continue
                    if id not in self.req_ids and id not in self.outbound_ids:
                        print("Discarding invalid message with bogus ID: %s" %
                              repr(msg))
                        if type == "CHUNK_HEADER":
                            print("Discarding binary data chunk")
                            marshal.load(self.proc.stdout)
                        continue

                try:
                    if type == "CHUNK_HEADER":
                        x = marshal.load(self.proc.stdout)
                        send_with_header(msg, x)
                        if msg.get("lastChunk"):
                            self.transmitted_media(msg['requestId'],
                                                   msg['mediaId'])
                    else:

                        if "response" in msg and self.enable_profiling.get(id):
                            p = msg.get("profile", None)
                            msg["profile"] = {
                                "origin":
                                "Server (Python)",
                                "description":
                                "Downlink dispatch",
                                "start-time":
                                float(self.start_time.get(id, 0) * 1000),
                                "end-time":
                                float(time.time() * 1000)
                            }
                            if p is not None:
                                msg["profile"]["children"] = [p]

                                for o in msg.get("objects", []):
                                    if o["path"][0] == "profile":
                                        o["path"].insert(1, "children")
                                        o["path"].insert(2, 0)

                        if "response" in msg and msg[
                                'id'] == 'pre-kill-task-state':
                            # Special case handling for a "clean" kill (where we manage to recover the state)

                            objects = msg.get('objects', [])
                            for o in objects:
                                if 'path' in o and o['path'][0] == 'response':
                                    o['path'][0] = 'taskState'
                                if 'DataMedia' in o['type']:
                                    msg['objects'] = []
                                    msg['response'] = None
                                    break

                            self.req_ids.discard('pre-kill-task-state')

                            send_with_header({
                                'type': 'NOTIFY_TASK_KILLED',
                                'id': list(self.req_ids)[0],
                                'taskState': msg['response'],
                                'objects': objects
                            })

                            self.proc.terminate()
                        else:
                            send_with_header(msg)

                    if "response" in msg or "error" in msg:
                        #if statsd and (id in self.start_time):
                        #    statsd.timing('Downlink.WorkerLifetime', (time.time()*1000) - self.start_time.get(id, 0)*1000)
                        self.on_media_complete(msg, lambda: self.responded(id))

                except UnicodeError:
                    send_with_header({
                        "id": id,
                        "error": {
                            "type":
                            "UnicodeError",
                            "message":
                            "This function returned a binary string (not text). If you want to return binary data, use a BlobMedia object instead."
                        }
                    })
                    self.responded(id)

        except EOFError:
            print(
                "EOFError while reading worker stdout. This should not have happened."
            )
            pass

        finally:
            for i in self.req_ids:
                workers_by_id.pop(i, None)
            for i in self.outbound_ids.keys():
                workers_by_id.pop(i, None)
            rt = self.proc.poll()
            if rt is None:
                self.proc.terminate()
            for _, t in self.timeouts.items():
                t.cancel()
            if self.cache_key is not None and cached_workers.get(
                    self.cache_key, (None, None))[1] is self:
                cached_workers.pop(self.cache_key, None)

            error_id = "".join(
                [random.choice('0123456789abcdef') for x in range(10)])
            for i in self.req_ids:
                if self.timed_out:
                    message = 'Server code took too long'
                    type = "anvil.server.TimeoutError"
                elif rt == -9:
                    message = 'Server code execution process was killed. It may have run out of memory: %s' % (
                        error_id)
                    type = "anvil.server.ExecutionTerminatedError"
                    sys.stderr.write(message + " (IDs %s)\n" % i)
                    sys.stderr.flush()
                else:
                    message = 'Server code exited unexpectedly: %s' % (
                        error_id)
                    type = "anvil.server.ExecutionTerminatedError"
                    sys.stderr.write(message + " (IDs %s)\n" % i)
                    sys.stderr.flush()
                send_with_header({
                    'id': i,
                    'error': {
                        'type': type,
                        'message': message
                    }
                })
            print("Worker terminated for IDs %s (return code %s)" %
                  (self.req_ids, rt))
            maybe_quit_if_draining_and_done()