def indexValuesToSetAdds(self, indexValues):
        #indexValues contains (schema:typename:identity:fieldname -> indexHashVal) which builds
        #up the indices we need. We need to transpose to a dictionary ordered by the hash values,
        #not the identities

        t0 = time.time()
        heartbeatInterval = getHeartbeatInterval()

        setAdds = {}

        for iv in indexValues:
            val = indexValues[iv]

            if val is not None:
                schema_name, typename, identity, field_name = keymapping.split_data_reverse_index_key(
                    iv)

                index_key = keymapping.index_key_from_names_encoded(
                    schema_name, typename, field_name, val)

                setAdds.setdefault(index_key, set()).add(identity)

                #this could take a long time, so we need to keep heartbeating
                if time.time() - t0 > heartbeatInterval:
                    #note that this needs to be 'sendMessage' which sends immediately,
                    #not, 'write' which queues the message after this function finishes!
                    self._channel.sendMessage(ClientToServer.Heartbeat())
                    t0 = time.time()
        return setAdds
    def subscribeMultiple(self, subscriptionTuples, block=True):
        with self._lock:
            if self.disconnected.is_set():
                raise DisconnectedException()

            events = []

            for tup in subscriptionTuples:
                e = self._pendingSubscriptions.get(tup)

                if not e:
                    e = self._pendingSubscriptions[(
                        tup[0], tup[1], tup[2])] = threading.Event()

                assert tup[0] and tup[1]

                self._channel.write(
                    ClientToServer.Subscribe(schema=tup[0],
                                             typename=tup[1],
                                             fieldname_and_value=tup[2],
                                             isLazy=tup[3]))

                events.append(e)

        if not block:
            return tuple(events)

        for e in events:
            e.wait()

        with self._lock:
            if self.disconnected.is_set():
                raise DisconnectedException()

        return ()
Пример #3
0
    def authenticate(self, token):
        assert self._auth_token is None, "We already authenticated."
        self._auth_token = token

        self._channel.write(
            ClientToServer.Authenticate(token=token)
        )
    def addSchema(self, schema):
        schema.freeze()

        with self._lock:
            if schema.name in self._schemas:
                return

            self._schemas[schema.name] = schema

            schemaDesc = schema.toDefinition()

            self._channel.write(
                ClientToServer.DefineSchema(name=schema.name,
                                            definition=schemaDesc))
    def flush(self):
        """Make sure we know all transactions that have happened up to this point."""
        with self._lock:
            if self.disconnected.is_set():
                raise DisconnectedException()

            self._flushIx += 1
            ix = str(self._flushIx)
            e = self._flushEvents[ix] = threading.Event()
            self._channel.write(ClientToServer.Flush(guid=ix))

        e.wait()

        if self.disconnected.is_set():
            raise DisconnectedException()
    def _loadLazyObject(self, identity):
        e = self._lazy_object_read_blocks.get(identity)

        if e:
            return e

        e = self._lazy_object_read_blocks[identity] = threading.Event()

        self._channel.write(
            ClientToServer.LoadLazyObject(
                identity=identity,
                schema=self._lazy_objects[identity][0],
                typename=self._lazy_objects[identity][1]))

        return e
Пример #7
0
    def pumpMessagesFromClient(self):
        lastHeartbeat = time.time()
        while not self._shouldStop:
            if time.time() - lastHeartbeat > getHeartbeatInterval(
            ) and not self._stopHeartbeatingSet:
                lastHeartbeat = time.time()
                e = ClientToServer.Heartbeat()
            else:
                try:
                    e = self._clientToServerMsgQueue.get(timeout=1.0)
                except queue.Empty:
                    e = None

            if e:
                try:
                    self._serverCallback(e)
                except Exception:
                    traceback.print_exc()
                    self._logger.error("Pump thread failed: %s",
                                       traceback.format_exc())
                    return

        self._server.dropConnection(self)
Пример #8
0
    def addSchema(self, schema):
        schema.freeze()

        with self._lock:
            if schema not in self._schemas:
                self._schemas.add(schema)

                schemaDesc = schema.toDefinition()

                self._channel.write(
                    ClientToServer.DefineSchema(
                        name=schema.name,
                        definition=schemaDesc
                    )
                )

                self._schema_response_events[schema.name] = threading.Event()

            e = self._schema_response_events[schema.name]

        e.wait()

        if self.disconnected.is_set():
            raise DisconnectedException()
    def _onMessage(self, msg):
        self._messages_received += 1

        if msg.matches.Disconnected:
            with self._lock:
                self.disconnected.set()
                self.connectionObject = None

                for e in self._lazy_object_read_blocks.values():
                    e.set()

                for e in self._flushEvents.values():
                    e.set()

                for e in self._pendingSubscriptions.values():
                    e.set()

                for q in self._transaction_callbacks.values():
                    try:
                        q(TransactionResult.Disconnected())
                    except Exception:
                        self._logger.error(
                            "Transaction commit callback threw an exception:\n%s",
                            traceback.format_exc())

                self._transaction_callbacks = {}
                self._flushEvents = {}
        elif msg.matches.FlushResponse:
            with self._lock:
                e = self._flushEvents.get(msg.guid)
                if not e:
                    self._logger.error("Got an unrequested flush response: %s",
                                       msg.guid)
                else:
                    e.set()
        elif msg.matches.Initialize:
            with self._lock:
                self._cur_transaction_num = msg.transaction_num
                self.identityProducer = IdentityProducer(msg.identity_root)
                self.connectionObject = core_schema.Connection.fromIdentity(
                    msg.connIdentity)
                self.initialized.set()
        elif msg.matches.TransactionResult:
            with self._lock:
                try:
                    self._transaction_callbacks.pop(msg.transaction_guid)(
                        TransactionResult.Success() if msg.success else
                        TransactionResult.RevisionConflict(key=msg.badKey))
                except Exception:
                    self._logger.error(
                        "Transaction commit callback threw an exception:\n%s",
                        traceback.format_exc())
        elif msg.matches.Transaction:
            with self._lock:
                key_value = {}
                priors = {}

                writes = {k: msg.writes[k] for k in msg.writes}
                set_adds = {k: msg.set_adds[k] for k in msg.set_adds}
                set_removes = {k: msg.set_removes[k] for k in msg.set_removes}

                for k, val_serialized in writes.items():
                    if not self._suppressKey(k):
                        key_value[k] = val_serialized

                        priors[k] = self._versioned_data.setVersionedValue(
                            k, msg.transaction_id,
                            bytes.fromhex(val_serialized)
                            if val_serialized is not None else None)

                for k, a in set_adds.items():
                    a = self._suppressIdentities(k, set(a))

                    self._versioned_data.setVersionedAddsAndRemoves(
                        k, msg.transaction_id, a, set())

                for k, r in set_removes.items():
                    r = self._suppressIdentities(k, set(r))
                    self._versioned_data.setVersionedAddsAndRemoves(
                        k, msg.transaction_id, set(), r)

                self._cur_transaction_num = msg.transaction_id

                self._versioned_data.cleanup(self._cur_transaction_num)

            for handler in self._onTransactionHandlers:
                try:
                    handler(key_value, priors, set_adds, set_removes,
                            msg.transaction_id)
                except Exception:
                    self._logger.error(
                        "_onTransaction handler %s threw an exception:\n%s",
                        handler, traceback.format_exc())

        elif msg.matches.SubscriptionIncrease:
            with self._lock:
                subscribedIdentities = self._schema_and_typename_to_subscription_set.setdefault(
                    (msg.schema, msg.typename), set())
                if subscribedIdentities is not Everything:
                    subscribedIdentities.update(msg.identities)
        elif msg.matches.SubscriptionData:
            with self._lock:
                lookupTuple = (msg.schema, msg.typename,
                               msg.fieldname_and_value)

                if lookupTuple not in self._subscription_buildup:
                    self._subscription_buildup[lookupTuple] = {
                        'values': {},
                        'index_values': {},
                        'identities': None,
                        'markedLazy': False
                    }
                else:
                    assert not self._subscription_buildup[lookupTuple][
                        'markedLazy'], 'received non-lazy data for a lazy subscription'

                self._subscription_buildup[lookupTuple]['values'].update(
                    {k: msg.values[k]
                     for k in msg.values})
                self._subscription_buildup[lookupTuple]['index_values'].update(
                    {k: msg.index_values[k]
                     for k in msg.index_values})

                if msg.identities is not None:
                    if self._subscription_buildup[lookupTuple][
                            'identities'] is None:
                        self._subscription_buildup[lookupTuple][
                            'identities'] = set()
                    self._subscription_buildup[lookupTuple][
                        'identities'].update(msg.identities)
        elif msg.matches.LazyTransactionPriors:
            with self._lock:
                for k, v in msg.writes.items():
                    self._versioned_data.setVersionedTailValueStringified(
                        k,
                        bytes.fromhex(v) if v is not None else None)
        elif msg.matches.LazyLoadResponse:
            with self._lock:
                for k, v in msg.values.items():
                    self._versioned_data.setVersionedTailValueStringified(
                        k,
                        bytes.fromhex(v) if v is not None else None)

                self._lazy_objects.pop(msg.identity, None)

                e = self._lazy_object_read_blocks.pop(msg.identity, None)
                if e:
                    e.set()

        elif msg.matches.LazySubscriptionData:
            with self._lock:
                lookupTuple = (msg.schema, msg.typename,
                               msg.fieldname_and_value)

                assert lookupTuple not in self._subscription_buildup

                self._subscription_buildup[lookupTuple] = {
                    'values': {},
                    'index_values': msg.index_values,
                    'identities': msg.identities,
                    'markedLazy': True
                }

        elif msg.matches.SubscriptionComplete:
            with self._lock:
                event = self._pendingSubscriptions.get(
                    (msg.schema, msg.typename, tuple(msg.fieldname_and_value)
                     if msg.fieldname_and_value is not None else None))

                if not event:
                    self._logger.error(
                        "Received unrequested subscription to schema %s / %s / %s. have %s",
                        msg.schema, msg.typename, msg.fieldname_and_value,
                        self._pendingSubscriptions)
                    return

                lookupTuple = (msg.schema, msg.typename,
                               msg.fieldname_and_value)

                identities = self._subscription_buildup[lookupTuple][
                    'identities']
                values = self._subscription_buildup[lookupTuple]['values']
                index_values = self._subscription_buildup[lookupTuple][
                    'index_values']
                markedLazy = self._subscription_buildup[lookupTuple][
                    'markedLazy']
                del self._subscription_buildup[lookupTuple]

                sets = self.indexValuesToSetAdds(index_values)

                if msg.fieldname_and_value is None:
                    if msg.typename is None:
                        for tname in self._schemas[msg.schema]._types:
                            self._schema_and_typename_to_subscription_set[
                                msg.schema, tname] = Everything
                    else:
                        self._schema_and_typename_to_subscription_set[
                            msg.schema, msg.typename] = Everything
                else:
                    assert msg.typename is not None
                    subscribedIdentities = self._schema_and_typename_to_subscription_set.setdefault(
                        (msg.schema, msg.typename), set())
                    if subscribedIdentities is not Everything:
                        subscribedIdentities.update(identities)

                t0 = time.time()
                heartbeatInterval = getHeartbeatInterval()

                #this is a fault injection to allow us to verify that heartbeating during this
                #function will keep the server connection alive.
                for _ in range(self._largeSubscriptionHeartbeatDelay):
                    self._channel.sendMessage(ClientToServer.Heartbeat())
                    time.sleep(heartbeatInterval)

                totalBytes = 0
                for k, v in values.items():
                    if v is not None:
                        totalBytes += len(v)

                if totalBytes > 1000000:
                    self._logger.info(
                        "Subscription %s loaded %.2f mb of raw data.",
                        lookupTuple, totalBytes / 1024.0**2)

                if markedLazy:
                    schema_and_typename = lookupTuple[:2]
                    for i in identities:
                        self._lazy_objects[i] = schema_and_typename

                for key, val in values.items():
                    self._versioned_data.setVersionedValue(
                        key, msg.tid,
                        None if val is None else bytes.fromhex(val))

                    #this could take a long time, so we need to keep heartbeating
                    if time.time() - t0 > heartbeatInterval:
                        #note that this needs to be 'sendMessage' which sends immediately,
                        #not, 'write' which queues the message after this function finishes!
                        self._channel.sendMessage(ClientToServer.Heartbeat())
                        t0 = time.time()

                for key, setval in sets.items():
                    self._versioned_data.updateVersionedAdds(
                        key, msg.tid, set(setval))

                    #this could take a long time, so we need to keep heartbeating
                    if time.time() - t0 > heartbeatInterval:
                        #note that this needs to be 'sendMessage' which sends immediately,
                        #not, 'write' which queues the message after this function finishes!
                        self._channel.sendMessage(ClientToServer.Heartbeat())
                        t0 = time.time()

                #this should be inline with the stream of messages coming from the server
                assert self._cur_transaction_num <= msg.tid

                self._cur_transaction_num = msg.tid

                event.set()
        else:
            assert False, "unknown message type " + msg._which
 def authenticate(self, token):
     self._channel.write(ClientToServer.Authenticate(token=token))
    def _set_versioned_object_data(self, key_value, set_adds, set_removes,
                                   keys_to_check_versions,
                                   indices_to_check_versions, as_of_version,
                                   confirmCallback):
        assert confirmCallback is not None

        transaction_guid = self.identityProducer.createIdentity()

        self._transaction_callbacks[transaction_guid] = confirmCallback

        out_writes = {}

        for k, v in key_value.items():
            out_writes[k] = v.serializedByteRep.hex(
            ) if v.serializedByteRep is not None else None
            if len(out_writes) > 10000:
                self._channel.write(
                    ClientToServer.TransactionData(
                        writes=out_writes,
                        set_adds={},
                        set_removes={},
                        key_versions=(),
                        index_versions=(),
                        transaction_guid=transaction_guid))
                self._channel.write(ClientToServer.Heartbeat())
                out_writes = {}

        ct = 0
        out_set_adds = {}
        for k, v in set_adds.items():
            out_set_adds[k] = tuple(v)
            ct += len(v)

            if len(out_set_adds) > 10000 or ct > 100000:
                self._channel.write(
                    ClientToServer.TransactionData(
                        writes={},
                        set_adds=out_set_adds,
                        set_removes={},
                        key_versions=(),
                        index_versions=(),
                        transaction_guid=transaction_guid))
                self._channel.write(ClientToServer.Heartbeat())
                out_set_adds = {}
                ct = 0

        ct = 0
        out_set_removes = {}
        for k, v in set_removes.items():
            out_set_removes[k] = tuple(v)
            ct += len(v)

            if len(out_set_removes) > 10000 or ct > 100000:
                self._channel.write(
                    ClientToServer.TransactionData(
                        writes={},
                        set_adds={},
                        set_removes=out_set_removes,
                        key_versions=(),
                        index_versions=(),
                        transaction_guid=transaction_guid))
                self._channel.write(ClientToServer.Heartbeat())
                out_set_removes = {}
                ct = 0

        keys_to_check_versions = list(keys_to_check_versions)
        while len(keys_to_check_versions) > 10000:
            self._channel.write(
                ClientToServer.TransactionData(
                    writes={},
                    set_adds={},
                    set_removes={},
                    key_versions=keys_to_check_versions[:10000],
                    index_versions=(),
                    transaction_guid=transaction_guid))
            self._channel.write(ClientToServer.Heartbeat())
            keys_to_check_versions = keys_to_check_versions[10000:]

        indices_to_check_versions = list(indices_to_check_versions)
        while len(indices_to_check_versions) > 10000:
            self._channel.write(
                ClientToServer.TransactionData(
                    writes={},
                    set_adds={},
                    set_removes={},
                    key_versions=(),
                    index_versions=indices_to_check_versions[:10000],
                    transaction_guid=transaction_guid))
            indices_to_check_versions = indices_to_check_versions[10000:]

        self._channel.write(
            ClientToServer.TransactionData(
                writes=out_writes,
                set_adds=out_set_adds,
                set_removes=out_set_removes,
                key_versions=keys_to_check_versions,
                index_versions=indices_to_check_versions,
                transaction_guid=transaction_guid))

        self._channel.write(
            ClientToServer.CompleteTransaction(
                as_of_version=as_of_version,
                transaction_guid=transaction_guid))
Пример #12
0
 def heartbeat(self):
     if not self.disconnected and not self._stopHeartbeatingSet:
         self.sendMessage(ClientToServer.Heartbeat())
         self.loop.call_later(getHeartbeatInterval(), self.heartbeat)
Пример #13
0
    def _onMessage(self, msg):
        self._messages_received += 1

        if msg.matches.Disconnected:
            with self._lock:
                self.disconnected.set()
                self.connectionObject = None

                for e in self._lazy_object_read_blocks.values():
                    e.set()

                for e in self._flushEvents.values():
                    e.set()

                for e in self._pendingSubscriptions.values():
                    e.set()

                for e in self._schema_response_events.values():
                    e.set()

                for q in self._transaction_callbacks.values():
                    try:
                        q(TransactionResult.Disconnected())
                    except Exception:
                        self._logger.error(
                            "Transaction commit callback threw an exception:\n%s",
                            traceback.format_exc()
                        )

                self._transaction_callbacks = {}
                self._flushEvents = {}
        elif msg.matches.FlushResponse:
            with self._lock:
                e = self._flushEvents.get(msg.guid)
                if not e:
                    self._logger.error("Got an unrequested flush response: %s", msg.guid)
                else:
                    e.set()
        elif msg.matches.Initialize:
            with self._lock:
                self._cur_transaction_num = msg.transaction_num
                self._connection_state.setIdentityRoot(IDENTITY_BLOCK_SIZE * msg.identity_root)
                self.connectionObject = core_schema.Connection.fromIdentity(msg.connIdentity)
                self.initialized.set()
        elif msg.matches.TransactionResult:
            with self._lock:
                try:
                    self._transaction_callbacks.pop(msg.transaction_guid)(
                        TransactionResult.Success() if msg.success else
                        TransactionResult.RevisionConflict(key=msg.badKey)
                    )
                except Exception:
                    self._logger.error(
                        "Transaction commit callback threw an exception:\n%s",
                        traceback.format_exc()
                    )
        elif msg.matches.Transaction:
            with self._lock:
                self._markSchemaAndTypeMaxTids(set(k.fieldId for k in msg.writes), msg.transaction_id)

                self._connection_state.incomingTransaction(
                    msg.transaction_id,
                    msg.writes,
                    msg.set_adds,
                    msg.set_removes
                )

                self._cur_transaction_num = msg.transaction_id

            for handler in list(self._onTransactionHandlers):
                try:
                    handler(msg.writes, msg.set_adds, msg.set_removes, msg.transaction_id)
                except Exception:
                    self._logger.error(
                        "_onTransaction handler %s threw an exception:\n%s",
                        handler,
                        traceback.format_exc()
                    )

        elif msg.matches.SchemaMapping:
            with self._lock:
                for fieldDef, fieldId in msg.mapping.items():
                    self._field_id_to_field_def[fieldId] = fieldDef
                    self._fields_to_field_ids[fieldDef] = fieldId

                    self._field_id_to_schema_and_typename[fieldId] = (fieldDef.schema, fieldDef.fieldname)

                    self._connection_state.setFieldId(
                        fieldDef.schema,
                        fieldDef.typename,
                        fieldDef.fieldname,
                        fieldId
                    )

                self._schema_response_events[msg.schema].set()

        elif msg.matches.SubscriptionIncrease:
            with self._lock:
                for oid in msg.identities:
                    self._connection_state.markObjectSubscribed(oid, msg.transaction_id)

        elif msg.matches.SubscriptionData:
            with self._lock:
                lookupTuple = (msg.schema, msg.typename, msg.fieldname_and_value)

                if lookupTuple not in self._subscription_buildup:
                    self._subscription_buildup[lookupTuple] = {'values': {}, 'index_values': {}, 'identities': None, 'markedLazy': False}
                else:
                    assert not self._subscription_buildup[lookupTuple]['markedLazy'], 'received non-lazy data for a lazy subscription'

                self._subscription_buildup[lookupTuple]['values'].update({k: msg.values[k] for k in msg.values})
                self._subscription_buildup[lookupTuple]['index_values'].update({k: msg.index_values[k] for k in msg.index_values})

                if msg.identities is not None:
                    if self._subscription_buildup[lookupTuple]['identities'] is None:
                        self._subscription_buildup[lookupTuple]['identities'] = set()
                    self._subscription_buildup[lookupTuple]['identities'].update(msg.identities)
        elif msg.matches.LazyTransactionPriors:
            with self._lock:
                self._connection_state.incomingTransaction(self._connection_state.getMinTid(), msg.writes, {}, {})

        elif msg.matches.LazyLoadResponse:
            with self._lock:
                self._connection_state.incomingTransaction(self._connection_state.getMinTid(), msg.values, {}, {})

                self._connection_state.markObjectNotLazy(msg.identity)

                e = self._lazy_object_read_blocks.pop(msg.identity, None)

                if e:
                    e.set()

        elif msg.matches.LazySubscriptionData:
            with self._lock:
                lookupTuple = (msg.schema, msg.typename, msg.fieldname_and_value)

                assert lookupTuple not in self._subscription_buildup

                self._subscription_buildup[lookupTuple] = {
                    'values': {},
                    'index_values': msg.index_values,
                    'identities': msg.identities,
                    'markedLazy': True
                }

        elif msg.matches.SubscriptionComplete:
            with self._lock:
                event = self._pendingSubscriptions.get((
                    msg.schema,
                    msg.typename,
                    tuple(msg.fieldname_and_value) if msg.fieldname_and_value is not None else None
                ))

                if not event:
                    self._logger.error(
                        "Received unrequested subscription to schema %s / %s / %s. have %s",
                        msg.schema, msg.typename, msg.fieldname_and_value, self._pendingSubscriptions
                    )
                    return

                lookupTuple = (msg.schema, msg.typename, msg.fieldname_and_value)

                identities = self._subscription_buildup[lookupTuple]['identities']
                values = self._subscription_buildup[lookupTuple]['values']
                index_values = self._subscription_buildup[lookupTuple]['index_values']
                markedLazy = self._subscription_buildup[lookupTuple]['markedLazy']
                del self._subscription_buildup[lookupTuple]

                self._markSchemaAndTypeMaxTids(set(v.fieldId for v in values), msg.tid)

                sets = self._indexValuesToSetAdds(index_values)

                if msg.fieldname_and_value is None:
                    if msg.typename is None:
                        for typename in self._schemaToType[msg.schema]:
                            self._connection_state.markTypeSubscribed(msg.schema, typename, msg.tid)
                    else:
                        self._connection_state.markTypeSubscribed(msg.schema, msg.typename, msg.tid)
                else:
                    assert msg.typename is not None
                    for oid in identities:
                        self._connection_state.markObjectSubscribed(oid, msg.tid)

                heartbeatInterval = getHeartbeatInterval()

                # this is a fault injection to allow us to verify that heartbeating during this
                # function will keep the server connection alive.
                for _ in range(self._largeSubscriptionHeartbeatDelay):
                    self._channel.sendMessage(
                        ClientToServer.Heartbeat()
                    )
                    time.sleep(heartbeatInterval)

                if markedLazy:
                    schema, typename = lookupTuple[:2]

                    for i in identities:
                        self._connection_state.markObjectLazy(schema, typename, i)

                self._connection_state.incomingTransaction(msg.tid, values, sets, {})

                # this should be inline with the stream of messages coming from the server
                assert self._cur_transaction_num <= msg.tid

                self._cur_transaction_num = msg.tid

                event.set()
        else:
            assert False, "unknown message type " + msg._which