Beispiel #1
0
    def send(self, msg, bindata=None):
        id = msg.get("id")
        if msg.get("type") in [
                "CALL", "CALL_WITH_APP", "GET_TASK_STATE", "LAUNCH_REPL",
                "REPL_COMMAND", "TERMINATE_REPL"
        ]:
            # It's a new request! Start the timeout
            #print ("Setting timeout and routing for new request ID %s" % id)
            workers_by_id[id] = self
            if msg.get("enable-profiling"):
                self.enable_profiling[id] = True
                self.start_time[id] = time.time()
            self.req_ids.add(id)
            if msg["type"] != "REPL_COMMAND":
                self.set_timeout(id)

        elif msg.get("type") == "REPL_KEEPALIVE":
            self.clear_timeout(msg["repl"])
            self.set_timeout(msg["repl"])
            send_with_header({"id": id, "response": None})
            return

        self.to_worker.send(msg, bindata)

        def outbound_done():
            self.outbound_ids.pop(id, None)
            workers_by_id.pop(id, None)
            maybe_quit_if_draining_and_done()

        if "response" in msg or "error" in msg:
            self.on_media_complete(msg, outbound_done)
Beispiel #2
0
 def _hard_kill_background_task(self):
     try:
         self.req_ids.discard('pre-kill-task-state')
         send_with_header({
             'type': 'NOTIFY_TASK_KILLED',
             'id': list(self.req_ids)[0]
         })
     finally:
         self.hard_timeout()
Beispiel #3
0
 def _hard_kill_background_task(self):
     print("TIMEOUT KILLING BACKGROUND TASK %s" % self.req_ids)
     try:
         self.req_ids.discard('pre-kill-task-state')
         send_with_header({
             'type': 'NOTIFY_TASK_KILLED',
             'id': list(self.req_ids)[0]
         })
     finally:
         self.hard_timeout()
Beispiel #4
0
 def _hard_kill_background_task(self):
     print("HARD KILL BACKGROUND TASK %s" % self.initial_req_id)
     try:
         self.req_ids.discard('pre-kill-task-state')
         send_with_header({
             'type': 'NOTIFY_TASK_KILLED',
             'id': self.initial_req_id
         })
     finally:
         self.hard_timeout()
Beispiel #5
0
def launch(data):
    type = data["type"] if 'type' in data else None
    id = data["id"] if 'id' in data else None

    print("Calling function '%s' for app '%s' (ID %s)" % (data.get(
        "command", "<LiveObjectCall>"), data.get("app-id", "<unknown>"), id))
    persist_key = data.get("persist-key")

    start_time = time.time()

    is_background_task = (type == "LAUNCH_BACKGROUND_WITH_APP")
    cache_key = None
    worker = None
    version = None
    supplied_version = data.get("app-version")
    if CAN_PERSIST and not is_background_task and persist_key is not None and "app-id" in data and supplied_version is not None:
        cache_key = repr((data["app-id"], persist_key))
        #print("Attempt persistence: %s" % cache_key)
        version, worker = cached_workers.get(cache_key, (None, None))
        #print("Version %s:\n%s\nvs\n%s" % (("MATCH" if version==supplied_version else "MISMATCH"), version, supplied_version))

    if data['command'] == "anvil.private.pdf.get_component" and not is_background_task:
        wid = data['args'][0][0]
        worker = workers_by_id.get(wid)
        if worker is None:
            send_with_header({
                'id': id,
                'error': {
                    'message':
                    "No component worker found for print call '%s'" % wid
                }
            })
            return

    elif worker is None or version != supplied_version:
        worker = Worker(id,
                        data.get("enable-profiling", False),
                        data.get("app-id", "<unknown>"),
                        cache_key=cache_key,
                        app_version=supplied_version,
                        set_timeout=not is_background_task)

    worker.start_time[id] = start_time

    worker.send(data)
Beispiel #6
0
    def send(self, msg, bin=False):

        if not bin:
            id = msg.get("id")
            if msg.get("type") in [
                    "CALL", "CALL_WITH_APP", "GET_TASK_STATE", "LAUNCH_REPL",
                    "REPL_COMMAND", "TERMINATE_REPL"
            ]:
                # It's a new request! Start the timeout
                #print ("Setting timeout and routing for new request ID %s" % id)
                workers_by_id[id] = self
                if msg.get("enable-profiling"):
                    self.enable_profiling[id] = True
                    self.start_time[id] = time.time()
                self.req_ids.add(id)
                if msg["type"] != "REPL_COMMAND":
                    self.set_timeout(id)

            elif msg.get("type") == "REPL_KEEPALIVE":
                self.clear_timeout(msg["repl"])
                self.set_timeout(msg["repl"])
                send_with_header({"id": id, "response": None})
                return

            # A horrid hack - a one-char "activation" that's not marshalled, because marshal holds the GIL and Windows doesn't support select() on pipes and urrrggghhh

            d = b"X" + marshal.dumps(msg)
        else:
            d = marshal.dumps(msg)

        self.proc.stdin.write(d)
        self.proc.stdin.flush()

        if not bin:

            def outbound_done():
                self.outbound_ids.pop(id, None)
                workers_by_id.pop(id, None)
                maybe_quit_if_draining_and_done()

            if "response" in msg or "error" in msg:
                self.on_media_complete(msg, outbound_done)
Beispiel #7
0
def launch(data):
    type = data.get("type")
    id = data.get("id")

    persist_key = data.get("persist-key")

    start_time = time.time()

    is_background_task = (type in [
        "LAUNCH_BACKGROUND", "LAUNCH_BACKGROUND_WITH_APP"
    ])
    is_repl_launch = type == "LAUNCH_REPL"
    cache_key = None
    worker = None
    version = None
    supplied_version = data.get("app-version")

    print("%s '%s' for app '%s' (ID %s)" %
          ("Launching REPL" if is_repl_launch else
           "Launching BG task" if is_background_task else "Calling function",
           data.get("command", "<no func>"), data.get("app-id",
                                                      "<unknown>"), id))

    if CAN_PERSIST and not is_background_task and persist_key is not None and "app-id" in data and supplied_version is not None:
        cache_key = repr((data["app-id"], persist_key))
        #print("Attempt persistence: %s" % cache_key)
        version, worker = cached_workers.get(cache_key, (None, None))
        #print("Version %s:\n%s\nvs\n%s" % (("MATCH" if version==supplied_version else "MISMATCH"), version, supplied_version))

    if data.get(
            'command'
    ) == "anvil.private.pdf.get_component" and not is_background_task:
        wid = data['args'][0][0]
        worker = workers_by_id.get(wid)
        if worker is None:
            send_with_header({
                'id': id,
                'error': {
                    'message':
                    "No component worker found for print call '%s'" % wid
                }
            })
            return

    elif worker is None or version != supplied_version:
        worker = Worker(
            id,
            data.get("enable-profiling", False),
            data.get("app-id", "<unknown>"),
            cache_key=cache_key,
            app_version=supplied_version,
            set_timeout=not is_background_task and not is_repl_launch,
            task_info={
                "app_id":
                data["app-id"],
                "type":
                "repl"
                if is_repl_launch else "background_task" if is_background_task
                else "persistent_worker" if cache_key else "server_call",
                "task":
                data.get("command"),
                "persist": {
                    "key": persist_key,
                    "version": supplied_version
                } if cache_key else None,
            })

    worker.send(data)
Beispiel #8
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()