diff --git a/src/box/alter.cc b/src/box/alter.cc index 49f315dde7226710b5c48b4b1b5f8d56818379e1..a1785d78e3f46cda41b996e24328c347d585a108 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -290,13 +290,6 @@ static void alter_space_commit(struct trigger *trigger, void * /* event */) { struct alter_space *alter = (struct alter_space *) trigger->data; -#if 0 - /* - * Clear the lock first - should there be an - * exception/bug, the lock must not be left around. - */ - space_unset_on_replace(space, alter); -#endif /* * If an index is unchanged, all its properties, including * ID are intact. Move this index here. If an index is @@ -444,18 +437,18 @@ alter_space_do(struct txn *txn, struct alter_space *alter, op->alter_def(alter); /* * Create a new (empty) space for the new definition. - * Sic: the space engine is not the same yet, the - * triggers are not set. + * Sic: the triggers are not moved over yet. */ alter->new_space = space_new(&alter->space_def, &alter->key_list); /* - * Copy the engine, the new space is at the same recovery - * phase as the old one. Do it before performing the alter, - * since engine.recover does different things depending on - * the recovery phase. + * Copy the replace function, the new space is at the same recovery + * phase as the old one. This hack is especially necessary for + * system spaces, which may be altered in some row in the + * snapshot/xlog, but needs to continue staying "fully + * built". */ - alter->new_space->handler->recovery = - alter->old_space->handler->recovery; + alter->new_space->handler->replace = + alter->old_space->handler->replace; memcpy(alter->new_space->access, alter->old_space->access, sizeof(alter->old_space->access)); @@ -506,12 +499,9 @@ ModifySpace::prepare(struct alter_space *alter) space_name(alter->old_space), "can not change space engine"); - engine_recovery *recovery = - &alter->old_space->handler->recovery; - if (def.field_count != 0 && def.field_count != alter->old_space->def.field_count && - recovery->state != READY_NO_KEYS && + space_index(alter->old_space, 0) != NULL && space_size(alter->old_space) > 0) { tnt_raise(ClientError, ER_ALTER_SPACE, @@ -526,7 +516,7 @@ ModifySpace::prepare(struct alter_space *alter) "space does not support temporary flag"); } if (def.temporary != alter->old_space->def.temporary && - recovery->state != READY_NO_KEYS && + space_index(alter->old_space, 0) != NULL && space_size(alter->old_space) > 0) { tnt_raise(ClientError, ER_ALTER_SPACE, space_name(alter->old_space), @@ -592,15 +582,15 @@ DropIndex::alter(struct alter_space *alter) space_name(alter->new_space)); } /* - * OK to drop the primary key. Put the space back to - * 'READY_NO_KEYS' state, so that: + * OK to drop the primary key. Inform the engine about it, + * since it may have to reset handler->replace function, + * so that: * - DML returns proper errors rather than crashes the - * server (thanks to engine_no_keys.replace), - * - When a new primary key is finally added, the space - * can be put back online properly with - * engine_no_keys.recover. + * server + * - when a new primary key is finally added, the space + * can be put back online properly. */ - alter->new_space->handler->initRecovery(); + alter->new_space->handler->engine->dropPrimaryKey(alter->new_space); } void @@ -779,20 +769,14 @@ on_replace_in_old_space(struct trigger *trigger, void *event) * anyway, so there is no need to fully populate index with data, * it is done at the end of recovery. * - * Note, that system spaces are exception to this, since + * Note, that system spaces are exception to this, since * they are fully enabled at all times. */ void AddIndex::alter(struct alter_space *alter) { - /* - * READY_NO_KEYS is when a space has no functional keys. - * Possible both during and after recovery. - */ - engine_recovery *recovery = - &alter->new_space->handler->recovery; - - if (recovery->state == READY_NO_KEYS) { + Engine *engine = alter->new_space->handler->engine; + if (space_index(alter->old_space, 0) == NULL) { if (new_key_def->iid == 0) { /* * Adding a primary key: bring the space @@ -804,46 +788,27 @@ AddIndex::alter(struct alter_space *alter) * key. After recovery, it means building * all keys. */ - recovery->recover(alter->new_space); + engine->addPrimaryKey(alter->new_space); } else { /* - * Adding a secondary key: nothing to do. - * Before the end of recovery, nothing to do - * because secondary keys are built in bulk later. - * During normal operation, nothing to do - * because without a primary key there is - * no data in the space, and secondary - * keys are built once the primary is - * added. - * TODO Consider prohibiting this branch - * altogether. + * Adding a secondary key. */ + tnt_raise(ClientError, ER_ALTER_SPACE, + space_name(alter->new_space), + "can not add a secondary key before primary"); } return; } + /** + * If it's a secondary key, and we're not building them + * yet (i.e. it's snapshot recovery for memtx), do nothing. + */ + if (new_key_def->iid != 0 && !engine->needToBuildSecondaryKey(alter->new_space)) + return; + Index *pk = index_find(alter->old_space, 0); Index *new_index = index_find(alter->new_space, new_key_def->iid); - /* READY_PRIMARY_KEY is a state that only occurs during WAL recovery. */ - if (recovery->state == READY_PRIMARY_KEY) { - if (new_key_def->iid == 0) { - /* - * Bulk rebuild of the new primary key - * from old primary key - it is safe to do - * in bulk and without tuple-by-tuple - * verification, since all tuples have - * been verified when inserted, before - * shutdown. - */ - index_build(new_index, pk); - } else { - /* - * No need to build a secondary key during - * WAL recovery. - */ - } - return; - } /* Now deal with any kind of add index during normal operation. */ struct iterator *it = pk->position(); pk->initIterator(it, ITER_ALL, NULL, 0); diff --git a/src/box/box.cc b/src/box/box.cc index df588cb508938764d3b115a7f97f5f877b28d2b3..ae8ce453c647b5935d7a39ecf9d95d4d5f9dfcda 100644 --- a/src/box/box.cc +++ b/src/box/box.cc @@ -444,6 +444,7 @@ box_init(void) engine_end_recover_snapshot(); } else if (recovery_has_remote(recovery)) { /* Initialize a new replica */ + engine_begin_join(); replica_bootstrap(recovery); engine_end_recover_snapshot(); box_snapshot(); diff --git a/src/box/engine.cc b/src/box/engine.cc index cd3d53fcbf5dafd9f08b7767bf82c9b77b2ce1c1..1930b2e8305c5d10b9de7199624a376f312c4d5a 100644 --- a/src/box/engine.cc +++ b/src/box/engine.cc @@ -56,11 +56,29 @@ void Engine::commit(struct txn*) void Engine::rollback(struct txn*) {} +void Engine::initSystemSpace(struct space * /* space */) +{ + panic("not implemented"); +} + +void +Engine::addPrimaryKey(struct space * /* space */) +{ +} + +void +Engine::dropPrimaryKey(struct space * /* space */) +{ +} + +bool Engine::needToBuildSecondaryKey(struct space * /* space */) +{ + return true; +} + Handler::Handler(Engine *f) :engine(f) { - /* derive recovery state from engine */ - initRecovery(); } /** Register engine instance. */ @@ -102,16 +120,13 @@ engine_begin_recover_snapshot(int64_t snapshot_lsn) } } -static void -do_one_recover_step(struct space *space, void * /* param */) +void +engine_begin_join() { - if (space_index(space, 0)) { - space->handler->recover(space); - } else { - /* in case of space has no primary index, - * derive it's engine handler recovery state from - * the global one. */ - space->handler->initRecovery(); + /* recover engine snapshot */ + Engine *engine; + engine_foreach(engine) { + engine->beginJoin(); } } @@ -126,7 +141,6 @@ engine_end_recover_snapshot() engine_foreach(engine) { engine->end_recover_snapshot(); } - space_foreach(do_one_recover_step, NULL); } void @@ -139,8 +153,6 @@ engine_end_recovery() Engine *engine; engine_foreach(engine) engine->end_recovery(); - - space_foreach(do_one_recover_step, NULL); } int @@ -179,3 +191,12 @@ engine_checkpoint(int64_t checkpoint_id) snapshot_is_in_progress = false; return save_errno; } + +void +engine_join(Relay *relay) +{ + Engine *engine; + engine_foreach(engine) { + engine->join(relay); + } +} diff --git a/src/box/engine.h b/src/box/engine.h index 0498e40e276f268267857e59d0e8cf26455c5a60..26f6a691e1375f1bfd2fa69f544d37dfb65dfbe8 100644 --- a/src/box/engine.h +++ b/src/box/engine.h @@ -32,6 +32,7 @@ struct space; struct tuple; +class Relay; enum engine_flags { ENGINE_NO_YIELD = 1, @@ -53,46 +54,10 @@ enum engine_flags { extern uint32_t engine_flags[BOX_ENGINE_MAX]; extern struct rlist engines; -/** Reflects what space_replace() is supposed to do. */ -enum engine_recovery_state { - /** - * The space is created, but has no data - * and no primary key, or, if there is a primary - * key, it's not ready for use (being built with - * buildNext()). - * Replace is always an error, since there are no - * indexes to add data to. - */ - READY_NO_KEYS, - /** - * The space has a functional primary key. - * Replace adds the tuple to this key. - */ - READY_PRIMARY_KEY, - /** - * The space is fully functional, all keys - * are fully built, replace adds its tuple - * to all keys. - */ - READY_ALL_KEYS -}; - -typedef void (*engine_recover_f)(struct space*); - typedef struct tuple * (*engine_replace_f)(struct space *, struct tuple *, struct tuple *, enum dup_replace_mode); -struct engine_recovery { - enum engine_recovery_state state; - /* Recover is called after each recover step to enable - * keys. When recovery is complete, it enables all keys - * at once and resets itself to a no-op. - */ - engine_recover_f recover; - engine_replace_f replace; -}; - struct Handler; /** Engine instance */ @@ -104,6 +69,7 @@ class Engine: public Object { virtual void init(); /** Create a new engine instance for a space. */ virtual Handler *open() = 0; + virtual void initSystemSpace(struct space *space); /** * Check a key definition for violation of * various limits. @@ -114,10 +80,31 @@ class Engine: public Object { * space. */ virtual Index *createIndex(struct key_def*) = 0; + /** + * Called by alter when a primary key added, + * after createIndex is invoked for the new + * key. + */ + virtual void addPrimaryKey(struct space *space); /** * Delete all tuples in the index on drop. */ - virtual void dropIndex(Index*) = 0; + virtual void dropIndex(Index *) = 0; + /** + * Called by alter when the primary key is dropped. + * Do whatever is necessary with space/handler object, + * e.g. disable handler replace function, if + * necessary. + */ + virtual void dropPrimaryKey(struct space *space); + /** + * An optimization for MemTX engine, which + * builds all secondary keys after recovery from + * a snapshot. + */ + virtual bool needToBuildSecondaryKey(struct space *space); + + virtual void join(Relay *) = 0; /** * Engine specific transaction life-cycle routines. */ @@ -133,6 +120,10 @@ class Engine: public Object { * state change (end of recovery from the binary log). */ virtual void end_recovery() = 0; + /** + * Notify engine about a JOIN start (slave-side) + */ + virtual void beginJoin() = 0; /** * Begin a two-phase snapshot creation in this * engine (snapshot is a memtx idea of a checkpoint). @@ -159,7 +150,6 @@ class Engine: public Object { uint32_t id; /** Engine flags */ uint32_t flags; - struct engine_recovery recovery; /** Used for search for engine by name. */ struct rlist link; }; @@ -171,23 +161,8 @@ struct Handler: public Object { Handler(Engine *f); virtual ~Handler() {} - inline struct tuple * - replace(struct space *space, - struct tuple *old_tuple, - struct tuple *new_tuple, enum dup_replace_mode mode) - { - return recovery.replace(space, old_tuple, new_tuple, mode); - } - - inline void recover(struct space *space) { - recovery.recover(space); - } - - inline void initRecovery() { - recovery = engine->recovery; - } + engine_replace_f replace; - engine_recovery recovery; Engine *engine; }; @@ -234,6 +209,12 @@ engine_id(Handler *space) void engine_begin_recover_snapshot(int64_t snapshot_lsn); +/** + * Called at the start of JOIN routine. + */ +void +engine_begin_join(); + /** * Called at the end of recovery from snapshot. * Build primary keys in all spaces. @@ -254,4 +235,10 @@ engine_end_recovery(); int engine_checkpoint(int64_t checkpoint_id); +/** + * Send a snapshot. + */ +void +engine_join(Relay *); + #endif /* TARANTOOL_BOX_ENGINE_H_INCLUDED */ diff --git a/src/box/lua/call.cc b/src/box/lua/call.cc index 8c27328020cdb89423aa144ec6bee1be5a757473..cc92cf7b75fbfe8de9d24636bd4a1fdb4ef12c24 100644 --- a/src/box/lua/call.cc +++ b/src/box/lua/call.cc @@ -360,10 +360,15 @@ lbox_commit(lua_State * /* L */) * Do nothing if transaction is not started, * it's the same as BEGIN + COMMIT. */ - if (txn) { + if (! txn) + return 0; + try { txn_commit(txn); - txn_finish(txn); + } catch (...) { + txn_rollback(); + throw; } + txn_finish(txn); return 0; } @@ -615,6 +620,13 @@ execute_call(lua_State *L, struct request *request, struct obuf *out) void box_lua_call(struct request *request, struct obuf *out) { + auto txn_guard = make_scoped_guard([=] { + struct txn *txn = in_txn(); + if (txn) { + say_warn("transaction is active at CALL return"); + txn_rollback(); + } + }); lua_State *L = NULL; try { L = lua_newthread(tarantool_L); diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua index be134f513847b37597850c49776af67349e09f96..55ee8e14fdada0b749ba62297ed5873f90e3231a 100644 --- a/src/box/lua/load_cfg.lua +++ b/src/box/lua/load_cfg.lua @@ -21,7 +21,8 @@ local default_sophia_cfg = { memory_limit = 0, threads = 5, node_size = 134217728, - page_size = 131072 + page_size = 131072, + compression = "none" } -- all available options @@ -65,7 +66,8 @@ local sophia_template_cfg = { memory_limit = 'number', threads = 'number', node_size = 'number', - page_size = 'number' + page_size = 'number', + compression = 'string' } -- types of available options diff --git a/src/box/memtx_engine.cc b/src/box/memtx_engine.cc index b64baafd6575abf3795d911028968bd82a5ee853..d0bdd0ec8425993b59b377d5338a82190d114254 100644 --- a/src/box/memtx_engine.cc +++ b/src/box/memtx_engine.cc @@ -27,6 +27,7 @@ * SUCH DAMAGE. */ #include "memtx_engine.h" +#include "replication.h" #include "tuple.h" #include "txn.h" #include "index.h" @@ -36,6 +37,7 @@ #include "memtx_bitset.h" #include "space.h" #include "salad/rlist.h" +#include "request.h" #include <stdlib.h> #include <string.h> #include "box.h" @@ -55,11 +57,27 @@ static struct slab_arena memtx_index_arena; static struct slab_cache memtx_index_arena_slab_cache; static struct mempool memtx_index_extent_pool; +/** + * A version of space_replace for a space which has + * no indexes (is not yet fully built). + */ +struct tuple * +memtx_replace_no_keys(struct space *space, struct tuple * /* old_tuple */, + struct tuple * /* new_tuple */, + enum dup_replace_mode /* mode */) +{ + Index *index = index_find(space, 0); + assert(index == NULL); /* not reached. */ + (void) index; + return NULL; /* replace found no old tuple */ +} struct MemtxSpace: public Handler { MemtxSpace(Engine *e) : Handler(e) - { } + { + replace = memtx_replace_no_keys; + } virtual ~MemtxSpace() { /* do nothing */ @@ -67,48 +85,151 @@ struct MemtxSpace: public Handler { } }; + /** - * This is a vtab with which a newly created space which has no - * keys is primed. - * At first it is set to correctly work for spaces created during - * recovery from snapshot. In process of recovery it is updated as - * below: - * - * 1) after SNAP is loaded: - * recover = space_build_primary_key - * 2) when all XLOGs are loaded: - * recover = space_build_all_keys -*/ + * A short-cut version of space_replace() used during bulk load + * from snapshot. + */ +struct tuple * +memtx_replace_build_next(struct space *space, struct tuple *old_tuple, + struct tuple *new_tuple, enum dup_replace_mode mode) +{ + assert(old_tuple == NULL && mode == DUP_INSERT); + (void) mode; + if (old_tuple) { + /* + * Called from txn_rollback() In practice + * is impossible: all possible checks for tuple + * validity are done before the space is changed, + * and WAL is off, so this part can't fail. + */ + panic("Failed to commit transaction when loading " + "from snapshot"); + } + space->index[0]->buildNext(new_tuple); + return NULL; /* replace found no old tuple */ +} -static inline void -memtx_recovery_prepare(struct engine_recovery *r) +/** + * A short-cut version of space_replace() used when loading + * data from XLOG files. + */ +struct tuple * +memtx_replace_primary_key(struct space *space, struct tuple *old_tuple, + struct tuple *new_tuple, enum dup_replace_mode mode) { - r->state = READY_NO_KEYS; - r->recover = space_begin_build_primary_key; - r->replace = space_replace_no_keys; + return space->index[0]->replace(old_tuple, new_tuple, mode); +} + +static struct tuple * +memtx_replace_all_keys(struct space *space, struct tuple *old_tuple, + struct tuple *new_tuple, enum dup_replace_mode mode) +{ + uint32_t i = 0; + try { + /* Update the primary key */ + Index *pk = index_find(space, 0); + assert(pk->key_def->is_unique); + /* + * If old_tuple is not NULL, the index + * has to find and delete it, or raise an + * error. + */ + old_tuple = pk->replace(old_tuple, new_tuple, mode); + + assert(old_tuple || new_tuple); + /* Update secondary keys. */ + for (i++; i < space->index_count; i++) { + Index *index = space->index[i]; + index->replace(old_tuple, new_tuple, DUP_INSERT); + } + return old_tuple; + } catch (Exception *e) { + /* Rollback all changes */ + for (; i > 0; i--) { + Index *index = space->index[i-1]; + index->replace(new_tuple, old_tuple, DUP_INSERT); + } + throw; + } + + assert(false); + return NULL; +} + +static void +memtx_end_build_primary_key(struct space *space, void *param) +{ + if (space->handler->engine != param || space_index(space, 0) == NULL || + space->handler->replace == memtx_replace_all_keys) + return; + + space->index[0]->endBuild(); + space->handler->replace = memtx_replace_primary_key; +} + +/** + * Secondary indexes are built in bulk after all data is + * recovered. This function enables secondary keys on a space. + * Data dictionary spaces are an exception, they are fully + * built right from the start. + */ +void +memtx_build_secondary_keys(struct space *space, void *param) +{ + if (space->handler->engine != param || space_index(space, 0) == NULL || + space->handler->replace == memtx_replace_all_keys) + return; + + if (space->index_id_max > 0) { + Index *pk = space->index[0]; + uint32_t n_tuples = pk->size(); + + if (n_tuples > 0) { + say_info("Building secondary indexes in space '%s'...", + space_name(space)); + } + + for (uint32_t j = 1; j < space->index_count; j++) + index_build(space->index[j], pk); + + if (n_tuples > 0) { + say_info("Space '%s': done", space_name(space)); + } + } + space->handler->replace = memtx_replace_all_keys; } MemtxEngine::MemtxEngine() :Engine("memtx"), m_snapshot_lsn(-1), - m_snapshot_pid(0) + m_snapshot_pid(0), + m_state(MEMTX_INITIALIZED) { flags = ENGINE_NO_YIELD | ENGINE_CAN_BE_TEMPORARY | ENGINE_AUTO_CHECK_UPDATE; - memtx_recovery_prepare(&recovery); +} + +/** Called at start to tell memtx to recover to a given LSN. */ +void +MemtxEngine::begin_recover_snapshot(int64_t /* lsn */) +{ + m_state = MEMTX_READING_SNAPSHOT; } void MemtxEngine::end_recover_snapshot() { - recovery.recover = space_build_primary_key; + m_state = MEMTX_READING_WAL; + space_foreach(memtx_end_build_primary_key, this); } void MemtxEngine::end_recovery() { - recovery.recover = space_build_all_keys; + m_state = MEMTX_OK; + space_foreach(memtx_build_secondary_keys, this); } Handler *MemtxEngine::open() @@ -116,6 +237,55 @@ Handler *MemtxEngine::open() return new MemtxSpace(this); } + +static void +memtx_add_primary_key(struct space *space, enum memtx_recovery_state state) +{ + switch (state) { + case MEMTX_INITIALIZED: + panic("can't create space"); + break; + case MEMTX_READING_SNAPSHOT: + space->index[0]->beginBuild(); + space->handler->replace = memtx_replace_build_next; + break; + case MEMTX_READING_WAL: + space->index[0]->beginBuild(); + space->index[0]->endBuild(); + space->handler->replace = memtx_replace_primary_key; + break; + case MEMTX_OK: + space->index[0]->beginBuild(); + space->index[0]->endBuild(); + space->handler->replace = memtx_replace_all_keys; + break; + } +} + +void +MemtxEngine::addPrimaryKey(struct space *space) +{ + memtx_add_primary_key(space, m_state); +} + +void +MemtxEngine::dropPrimaryKey(struct space *space) +{ + space->handler->replace = memtx_replace_no_keys; +} + +void +MemtxEngine::initSystemSpace(struct space *space) +{ + memtx_add_primary_key(space, MEMTX_OK); +} + +bool +MemtxEngine::needToBuildSecondaryKey(struct space *space) +{ + return space->handler->replace == memtx_replace_all_keys; +} + Index * MemtxEngine::createIndex(struct key_def *key_def) { @@ -231,25 +401,12 @@ MemtxEngine::rollback(struct txn *txn) } } -/** Called at start to tell memtx to recover to a given LSN. */ void -MemtxEngine::begin_recover_snapshot(int64_t /* lsn */) +MemtxEngine::beginJoin() { - /* - * memtx snapshotting supported directly by box. - * do nothing here. - */ + m_state = MEMTX_OK; } -/** The snapshot row metadata repeats the structure of REPLACE request. */ -struct request_replace_body { - uint8_t m_body; - uint8_t k_space_id; - uint8_t m_space_id; - uint32_t v_space_id; - uint8_t k_tuple; -} __attribute__((packed)); - static void snapshot_write_row(struct recovery_state *r, struct xlog *l, struct xrow_header *row) @@ -494,6 +651,12 @@ MemtxEngine::abort_checkpoint() m_snapshot_lsn = -1; } +void +MemtxEngine::join(Relay *relay) +{ + recover_snap(relay->r); +} + /** * Initialize arena for indexes. * The arena is used for memtx_index_extent_alloc diff --git a/src/box/memtx_engine.h b/src/box/memtx_engine.h index 127633f52604b7b63edb6b7c16798211f2a876c6..ba2540eee7e5462b01587004f1f92a5c2117878a 100644 --- a/src/box/memtx_engine.h +++ b/src/box/memtx_engine.h @@ -30,26 +30,40 @@ */ #include "engine.h" +enum memtx_recovery_state { + MEMTX_INITIALIZED, + MEMTX_READING_SNAPSHOT, + MEMTX_READING_WAL, + MEMTX_OK, +}; + struct MemtxEngine: public Engine { MemtxEngine(); virtual Handler *open(); virtual Index *createIndex(struct key_def *key_def); + virtual void addPrimaryKey(struct space *space); virtual void dropIndex(Index *index); + virtual void dropPrimaryKey(struct space *space); + virtual bool needToBuildSecondaryKey(struct space *space); virtual void keydefCheck(struct space *space, struct key_def *key_def); virtual void rollback(struct txn*); + virtual void beginJoin(); virtual void begin_recover_snapshot(int64_t lsn); virtual void end_recover_snapshot(); virtual void end_recovery(); + virtual void join(Relay*); virtual int begin_checkpoint(int64_t); virtual int wait_checkpoint(); virtual void commit_checkpoint(); virtual void abort_checkpoint(); + virtual void initSystemSpace(struct space *space); private: /** * LSN of the snapshot which is in progress. */ int64_t m_snapshot_lsn; pid_t m_snapshot_pid; + enum memtx_recovery_state m_state; }; enum { diff --git a/src/box/recovery.cc b/src/box/recovery.cc index 2694ce5d156eb94e54cf2bb56ef1dd4e431db89c..a4b8d662ac29bbf2f192a37dc238ceb93666b695 100644 --- a/src/box/recovery.cc +++ b/src/box/recovery.cc @@ -181,6 +181,7 @@ recovery_new(const char *snap_dirname, const char *wal_dirname, xdir_create(&r->wal_dir, wal_dirname, XLOG, &r->server_uuid); vclock_create(&r->vclock); + vclock_create(&r->vclock_join); xdir_scan(&r->snap_dir); /** @@ -375,7 +376,9 @@ recover_snap(struct recovery_state *r) say_info("recovering from `%s'", snap->filename); recover_xlog(r, snap); + /* Replace server vclock using the data from snapshot */ + vclock_copy(&r->vclock_join, &r->vclock); vclock_copy(&r->vclock, &snap->vclock); } diff --git a/src/box/recovery.h b/src/box/recovery.h index 40ee5ae9a91c6a6ff9d766cdf852369dea7aada0..ad4eabf3070cfef8a82ca0fe7d602462715cc34b 100644 --- a/src/box/recovery.h +++ b/src/box/recovery.h @@ -79,6 +79,7 @@ struct remote { }; struct recovery_state { + struct vclock vclock_join; struct vclock vclock; /** The WAL we're currently reading/writing from/to. */ struct xlog *current_wal; diff --git a/src/box/replication.cc b/src/box/replication.cc index 0306e8fa42e03a7a4d3031e2713f8bec41edd6d3..aec788cb7d166af96b32014cca054ea11fa4d885 100644 --- a/src/box/replication.cc +++ b/src/box/replication.cc @@ -32,8 +32,8 @@ #include "recovery.h" #include "xlog.h" -#include "evio.h" #include "iproto_constants.h" +#include "box/engine.h" #include "box/cluster.h" #include "box/schema.h" #include "box/vclock.h" @@ -44,34 +44,24 @@ #include "cfg.h" #include "trigger.h" -static void +void replication_send_row(struct recovery_state *r, void *param, - struct xrow_header *packet); - -/** State of a replication relay. */ -class Relay { -public: - /** Replica connection */ - struct ev_io io; - /* Request sync */ - uint64_t sync; - ev_tstamp wal_dir_rescan_delay; - struct recovery_state *r; - - Relay(int fd_arg, uint64_t sync_arg) - { - r = recovery_new(cfg_gets("snap_dir"), cfg_gets("wal_dir"), - replication_send_row, this); - coio_init(&io); - io.fd = fd_arg; - sync = sync_arg; - wal_dir_rescan_delay = cfg_getd("wal_dir_rescan_delay"); - } - ~Relay() - { - recovery_delete(r); - } -}; + struct xrow_header *packet); + +Relay::Relay(int fd_arg, uint64_t sync_arg) +{ + r = recovery_new(cfg_gets("snap_dir"), cfg_gets("wal_dir"), + replication_send_row, this); + coio_init(&io); + io.fd = fd_arg; + sync = sync_arg; + wal_dir_rescan_delay = cfg_getd("wal_dir_rescan_delay"); +} + +Relay::~Relay() +{ + recovery_delete(r); +} static inline void relay_set_cord_name(int fd) @@ -92,8 +82,9 @@ replication_join_f(va_list ap) struct recovery_state *r = relay->r; relay_set_cord_name(relay->io.fd); + /* Send snapshot */ - recover_snap(r); + engine_join(relay); /* Send response to JOIN command = end of stream */ struct xrow_header row; @@ -195,10 +186,19 @@ replication_subscribe(int fd, struct xrow_header *packet) cord_cojoin(&cord); } +void +relay_send(Relay *relay, struct xrow_header *packet) +{ + packet->sync = relay->sync; + struct iovec iov[XROW_IOVMAX]; + int iovcnt = xrow_to_iovec(packet, iov); + coio_writev(&relay->io, iov, iovcnt, 0); +} + /** Send a single row to the client. */ -static void +void replication_send_row(struct recovery_state *r, void *param, - struct xrow_header *packet) + struct xrow_header *packet) { Relay *relay = (Relay *) param; assert(iproto_type_is_dml(packet->type)); @@ -211,12 +211,8 @@ replication_send_row(struct recovery_state *r, void *param, * it not from the same server (i.e. don't send * replica's own rows back). */ - if (packet->server_id == 0 || packet->server_id != r->server_id) { - packet->sync = relay->sync; - struct iovec iov[XROW_IOVMAX]; - int iovcnt = xrow_to_iovec(packet, iov); - coio_writev(&relay->io, iov, iovcnt, 0); - } + if (packet->server_id == 0 || packet->server_id != r->server_id) + relay_send(relay, packet); /* * Update local vclock. During normal operation wal_write() * updates local vclock. In relay mode we have to update diff --git a/src/box/replication.h b/src/box/replication.h index d46fa04117b629e342eaaa978aad8b2f118bcf5f..d84c70c5443eccc3163cd50b89b97eea62b7875e 100644 --- a/src/box/replication.h +++ b/src/box/replication.h @@ -28,8 +28,23 @@ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +#include "evio.h" + struct xrow_header; +/** State of a replication relay. */ +class Relay { +public: + /** Replica connection */ + struct ev_io io; + /* Request sync */ + uint64_t sync; + struct recovery_state *r; + ev_tstamp wal_dir_rescan_delay; + Relay(int fd_arg, uint64_t sync_arg); + ~Relay(); +}; + void replication_join(int fd, struct xrow_header *packet); @@ -41,5 +56,8 @@ replication_join(int fd, struct xrow_header *packet); void replication_subscribe(int fd, struct xrow_header *packet); +void +relay_send(Relay *relay, struct xrow_header *packet); + #endif // TARANTOOL_REPLICATION_H_INCLUDED diff --git a/src/box/request.h b/src/box/request.h index b63f3dd74da04c797e85f2153dd55d09c1fcaab3..745f1e2ed8c03b958b6578eb646e35b766e2f633 100644 --- a/src/box/request.h +++ b/src/box/request.h @@ -62,6 +62,15 @@ struct request int field_base; }; +/** The snapshot row metadata repeats the structure of REPLACE request. */ +struct request_replace_body { + uint8_t m_body; + uint8_t k_space_id; + uint8_t m_space_id; + uint32_t v_space_id; + uint8_t k_tuple; +} __attribute__((packed)); + void request_create(struct request *request, uint32_t code); diff --git a/src/box/schema.cc b/src/box/schema.cc index e2db3c979fa131187dfa03c35358a25751142376..628093820515166a7728982db26c5d4c3bcc59e7 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -186,9 +186,7 @@ sc_space_new(struct space_def *space_def, * ensures validation of tuples when starting from * a snapshot of older version. */ - space->handler->recover(space); /* load snapshot - begin */ - space->handler->recover(space); /* load snapshot - end */ - space->handler->recover(space); /* build secondary keys */ + space->handler->engine->initSystemSpace(space); return space; } diff --git a/src/box/sophia_engine.cc b/src/box/sophia_engine.cc index 4be24222f0d4cb950cd43cc6246af0b3b919914a..5f387d0918c52079f26ca5bccc13700158b3f983 100644 --- a/src/box/sophia_engine.cc +++ b/src/box/sophia_engine.cc @@ -26,6 +26,7 @@ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ +#include "replication.h" #include "sophia_engine.h" #include "cfg.h" #include "xrow.h" @@ -34,7 +35,11 @@ #include "txn.h" #include "index.h" #include "sophia_index.h" +#include "recovery.h" #include "space.h" +#include "request.h" +#include "iproto_constants.h" +#include "replication.h" #include "salad/rlist.h" #include <sophia.h> #include <stdlib.h> @@ -71,51 +76,33 @@ void sophia_info(void (*callback)(const char*, const char*, void*), void *arg) sp_destroy(cur); } +static struct tuple * +sophia_replace(struct space *space, struct tuple *old_tuple, + struct tuple *new_tuple, + enum dup_replace_mode mode) +{ + Index *index = index_find(space, 0); + return index->replace(old_tuple, new_tuple, mode); +} + struct SophiaSpace: public Handler { SophiaSpace(Engine*); }; SophiaSpace::SophiaSpace(Engine *e) :Handler(e) -{ } - -static void -sophia_recovery_end(struct space *space) -{ - engine_recovery *r = &space->handler->recovery; - r->state = READY_ALL_KEYS; - r->replace = sophia_replace; - r->recover = space_noop; - /* - sophia_complete_recovery(space); - */ -} - -static void -sophia_recovery_end_snapshot(struct space *space) -{ - engine_recovery *r = &space->handler->recovery; - r->state = READY_PRIMARY_KEY; - r->recover = sophia_recovery_end; -} - -static void -sophia_recovery_begin_snapshot(struct space *space) { - engine_recovery *r = &space->handler->recovery; - r->recover = sophia_recovery_end_snapshot; + replace = sophia_replace; } SophiaEngine::SophiaEngine() :Engine("sophia") ,m_prev_checkpoint_lsn(-1) ,m_checkpoint_lsn(-1) + ,recovery_complete(0) { flags = 0; env = NULL; - recovery.state = READY_NO_KEYS; - recovery.recover = sophia_recovery_begin_snapshot; - recovery.replace = sophia_replace_recover; } void @@ -145,24 +132,114 @@ SophiaEngine::open() return new SophiaSpace(this); } +static inline void +sophia_snapshot_recover(void *env, int64_t lsn); + void SophiaEngine::end_recover_snapshot() { - recovery.replace = sophia_replace_recover; - recovery.recover = sophia_recovery_end_snapshot; + /* create snapshot reference after tarantool + * recovery, to ensure correct ref counting + * with spaces involved in snapshot. */ + if (m_checkpoint_lsn >= 0) { + sophia_snapshot_recover(env, m_checkpoint_lsn); + m_prev_checkpoint_lsn = m_checkpoint_lsn; + m_checkpoint_lsn = -1; + } +} + +static inline void +sophia_send_row(Relay *relay, uint32_t space_id, char *tuple, + uint32_t tuple_size) +{ + struct recovery_state *r = relay->r; + struct request_replace_body body; + body.m_body = 0x82; /* map of two elements. */ + body.k_space_id = IPROTO_SPACE_ID; + body.m_space_id = 0xce; /* uint32 */ + body.v_space_id = mp_bswap_u32(space_id); + body.k_tuple = IPROTO_TUPLE; + struct xrow_header row; + row.type = IPROTO_INSERT; + row.lsn = vclock_inc(&r->vclock_join, r->server_id); + row.server_id = 0; + row.bodycnt = 2; + row.body[0].iov_base = &body; + row.body[0].iov_len = sizeof(body); + row.body[1].iov_base = tuple; + row.body[1].iov_len = tuple_size; + relay_send(relay, &row); } +/** + * Relay all data that should be present in the snapshot + * to the replica. + */ +void +SophiaEngine::join(Relay *relay) +{ + struct vclock *res = vclockset_last(&relay->r->snap_dir.index); + if (res == NULL) + tnt_raise(ClientError, ER_MISSING_SNAPSHOT); + int64_t signt = vclock_signature(res); + + /* get snapshot object */ + char id[128]; + snprintf(id, sizeof(id), "snapshot.%" PRIu64, signt); + void *c = sp_ctl(env); + void *snapshot = sp_get(c, id); + assert(snapshot != NULL); + + /* iterate through a list of databases which took a + * part in the snapshot */ + void *db_cursor = sp_ctl(snapshot, "db_list"); + if (db_cursor == NULL) + sophia_raise(env); + while (sp_get(db_cursor)) { + void *db = sp_object(db_cursor); + + /* get space id */ + void *dbctl = sp_ctl(db); + void *oid = sp_get(dbctl, "name"); + char *name = (char*)sp_get(oid, "value", NULL); + char *pe = NULL; + uint32_t space_id = strtoul(name, &pe, 10); + sp_destroy(oid); + + /* send database */ + void *o = sp_object(db); + void *cursor = sp_cursor(snapshot, o); + if (cursor == NULL) { + sp_destroy(db_cursor); + sophia_raise(env); + } + while (sp_get(cursor)) { + o = sp_object(cursor); + uint32_t tuple_size = 0; + char *tuple = (char *)sp_get(o, "value", &tuple_size); + try { + sophia_send_row(relay, space_id, tuple, tuple_size); + } catch (...) { + sp_destroy(cursor); + sp_destroy(db_cursor); + throw; + } + } + sp_destroy(cursor); + } + sp_destroy(db_cursor); +} void SophiaEngine::end_recovery() { + if (recovery_complete) + return; /* complete two-phase recovery */ int rc = sp_open(env); if (rc == -1) sophia_raise(env); - recovery.state = READY_NO_KEYS; - recovery.replace = sophia_replace; - recovery.recover = space_noop; + recovery_complete = 1; } Index* @@ -279,8 +356,9 @@ SophiaEngine::commit(struct txn *txn) if (rc == -1) sophia_raise(env); rc = sp_commit(txn->engine_tx); - if (rc == -1) + if (rc == -1) { sophia_raise(env); + } assert(rc == 0); } @@ -293,6 +371,14 @@ SophiaEngine::rollback(struct txn *txn) txn->engine_tx = NULL; } +void +SophiaEngine::beginJoin() +{ + /* put engine to recovery-complete state to + * correctly support join */ + end_recovery(); +} + static inline void sophia_snapshot(void *env, int64_t lsn) { @@ -301,7 +387,7 @@ sophia_snapshot(void *env, int64_t lsn) int rc = sp_set(c, "scheduler.checkpoint"); if (rc == -1) sophia_raise(env); - char snapshot[32]; + char snapshot[128]; snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64, lsn); /* ensure snapshot is not already exists */ void *o = sp_get(c, snapshot); @@ -325,7 +411,7 @@ sophia_snapshot_recover(void *env, int64_t lsn) int rc = sp_set(c, "snapshot", snapshot_lsn); if (rc == -1) sophia_raise(env); - char snapshot[32]; + char snapshot[128]; snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64 ".lsn", lsn); rc = sp_set(c, snapshot, snapshot_lsn); if (rc == -1) @@ -336,7 +422,7 @@ static inline int sophia_snapshot_ready(void *env, int64_t lsn) { /* get sophia lsn associated with snapshot */ - char snapshot[32]; + char snapshot[128]; snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64 ".lsn", lsn); void *c = sp_ctl(env); void *o = sp_get(c, snapshot); @@ -363,7 +449,7 @@ sophia_snapshot_ready(void *env, int64_t lsn) static inline void sophia_delete_checkpoint(void *env, int64_t lsn) { - char snapshot[32]; + char snapshot[128]; snprintf(snapshot, sizeof(snapshot), "snapshot.%" PRIu64, lsn); void *c = sp_ctl(env); void *s = sp_get(c, snapshot); @@ -380,7 +466,7 @@ sophia_delete_checkpoint(void *env, int64_t lsn) void SophiaEngine::begin_recover_snapshot(int64_t lsn) { - sophia_snapshot_recover(env, lsn); + m_checkpoint_lsn = lsn; } int diff --git a/src/box/sophia_engine.h b/src/box/sophia_engine.h index ca037d4e7a94c0389fbd51367343baaa63d97e94..f48abf228a1b03fa3a778af20ead6c53636fab6f 100644 --- a/src/box/sophia_engine.h +++ b/src/box/sophia_engine.h @@ -40,9 +40,11 @@ struct SophiaEngine: public Engine { virtual void begin(struct txn*, struct space*); virtual void commit(struct txn*); virtual void rollback(struct txn*); + virtual void beginJoin(); virtual void begin_recover_snapshot(int64_t); virtual void end_recover_snapshot(); virtual void end_recovery(); + virtual void join(Relay*); virtual int begin_checkpoint(int64_t); virtual int wait_checkpoint(); virtual void commit_checkpoint(); @@ -51,6 +53,7 @@ struct SophiaEngine: public Engine { private: int64_t m_prev_checkpoint_lsn; int64_t m_checkpoint_lsn; + int recovery_complete; }; void sophia_info(void (*)(const char*, const char*, void*), void*); diff --git a/src/box/sophia_index.cc b/src/box/sophia_index.cc index aec713247ae935e6956ea62bbb163174142f061d..e7b47577c10c105d0e5f24447167cdd7996f5ca3 100644 --- a/src/box/sophia_index.cc +++ b/src/box/sophia_index.cc @@ -78,38 +78,6 @@ sophia_index_get(void *env, void *db, void *tx, const char *key, size_t keysize, return tuple_new(format, (char*)value, (char*)value + valuesize); } -struct tuple* -sophia_replace_recover(struct space *space, - struct tuple *old_tuple, struct tuple *new_tuple, - enum dup_replace_mode) -{ - SophiaIndex *index = (SophiaIndex*)index_find(space, 0); - struct txn *txn = in_txn(); - assert(txn != NULL && txn->engine_tx != NULL); - int rc; - if (old_tuple) { - rc = sophia_index_stmt(txn->engine_tx, index->db, 1, - index->key_def, - old_tuple); - } else { - rc = sophia_index_stmt(txn->engine_tx, index->db, 0, - index->key_def, - new_tuple); - } - if (rc == -1) - sophia_raise(index->env); - return NULL; -} - -struct tuple * -sophia_replace(struct space *space, - struct tuple *old_tuple, struct tuple *new_tuple, - enum dup_replace_mode mode) -{ - Index *index = index_find(space, 0); - return index->replace(old_tuple, new_tuple, mode); -} - static inline int sophia_index_compare(char *a, size_t asz __attribute__((unused)), char *b, size_t bsz __attribute__((unused)), @@ -139,6 +107,8 @@ sophia_configure(struct space *space, struct key_def *key_def) snprintf(pointer, sizeof(pointer), "pointer: %p", (void*)sophia_index_compare); snprintf(pointer_arg, sizeof(pointer_arg), "pointer: %p", (void*)key_def); sp_set(c, name, pointer, pointer_arg); + snprintf(name, sizeof(name), "db.%" PRIu32 ".compression", key_def->space_id); + sp_set(c, name, cfg_gets("sophia.compression")); snprintf(name, sizeof(name), "db.%" PRIu32, key_def->space_id); void *db = sp_get(c, name); if (db == NULL) @@ -166,16 +136,6 @@ SophiaIndex::SophiaIndex(struct key_def *key_def_arg __attribute__((unused))) tuple_format_ref(space->format, 1); } -void -sophia_complete_recovery(struct space *space) -{ - SophiaIndex *index = (SophiaIndex*)index_find(space, 0); - assert(space->handler->recovery.recover == space_noop); - int rc = sp_open(index->db); - if (rc == -1) - sophia_raise(index->env); -} - SophiaIndex::~SophiaIndex() { if (m_position != NULL) { @@ -258,7 +218,8 @@ SophiaIndex::findByKey(const char *key, uint32_t part_count) const mp_next(&keyptr); size_t keysize = keyptr - key; struct space *space = space_cache_find(key_def->space_id); - return sophia_index_get(env, db, NULL, key, keysize, space->format); + void *tx = in_txn() ? in_txn()->engine_tx : NULL; + return sophia_index_get(env, db, tx, key, keysize, space->format); } struct tuple * diff --git a/src/box/sophia_index.h b/src/box/sophia_index.h index f1d2752c6372d25ffcc831bd8f721a219f404573..8a9bacc0c08bc09bc958a47302e05bf003a1c5bd 100644 --- a/src/box/sophia_index.h +++ b/src/box/sophia_index.h @@ -52,17 +52,4 @@ class SophiaIndex: public Index { void *db; }; -struct tuple * -sophia_replace_recover(struct space*, - struct tuple*, struct tuple*, - enum dup_replace_mode); - -struct tuple * -sophia_replace(struct space*, - struct tuple*, struct tuple*, - enum dup_replace_mode); - -void -sophia_complete_recovery(struct space*); - #endif /* TARANTOOL_BOX_SOPHIA_INDEX_H_INCLUDED */ diff --git a/src/box/space.cc b/src/box/space.cc index ae197445b42d165a3b8f233d0fc430f03e21e4fd..85777d1eb394860e670bc675c04382f96dd52391 100644 --- a/src/box/space.cc +++ b/src/box/space.cc @@ -89,6 +89,7 @@ space_new(struct space_def *def, struct rlist *key_list) rlist_create(&space->on_replace); auto scoped_guard = make_scoped_guard([=] { + /** Ensure space_delete deletes all indexes. */ space_fill_index_map(space); space_delete(space); }); @@ -127,182 +128,17 @@ space_delete(struct space *space) free(space); } -/** - * A version of space_replace for a space which has - * no indexes (is not yet fully built). - */ -struct tuple * -space_replace_no_keys(struct space *space, struct tuple * /* old_tuple */, - struct tuple * /* new_tuple */, - enum dup_replace_mode /* mode */) -{ - Index *index = index_find(space, 0); - assert(index == NULL); /* not reached. */ - (void) index; - return NULL; /* replace found no old tuple */ -} - /** Do nothing if the space is already recovered. */ void space_noop(struct space * /* space */) {} -/** - * A short-cut version of space_replace() used during bulk load - * from snapshot. - */ -struct tuple * -space_replace_build_next(struct space *space, struct tuple *old_tuple, - struct tuple *new_tuple, enum dup_replace_mode mode) -{ - assert(old_tuple == NULL && mode == DUP_INSERT); - (void) mode; - if (old_tuple) { - /* - * Called from txn_rollback() In practice - * is impossible: all possible checks for tuple - * validity are done before the space is changed, - * and WAL is off, so this part can't fail. - */ - panic("Failed to commit transaction when loading " - "from snapshot"); - } - space->index[0]->buildNext(new_tuple); - return NULL; /* replace found no old tuple */ -} - -/** - * A short-cut version of space_replace() used when loading - * data from XLOG files. - */ -struct tuple * -space_replace_primary_key(struct space *space, struct tuple *old_tuple, - struct tuple *new_tuple, enum dup_replace_mode mode) -{ - return space->index[0]->replace(old_tuple, new_tuple, mode); -} - -static struct tuple * -space_replace_all_keys(struct space *space, struct tuple *old_tuple, - struct tuple *new_tuple, enum dup_replace_mode mode) -{ - uint32_t i = 0; - try { - /* Update the primary key */ - Index *pk = space->index[0]; - assert(pk->key_def->is_unique); - /* - * If old_tuple is not NULL, the index - * has to find and delete it, or raise an - * error. - */ - old_tuple = pk->replace(old_tuple, new_tuple, mode); - - assert(old_tuple || new_tuple); - /* Update secondary keys. */ - for (i++; i < space->index_count; i++) { - Index *index = space->index[i]; - index->replace(old_tuple, new_tuple, DUP_INSERT); - } - return old_tuple; - } catch (Exception *e) { - /* Rollback all changes */ - for (; i > 0; i--) { - Index *index = space->index[i-1]; - index->replace(new_tuple, old_tuple, DUP_INSERT); - } - throw; - } - - assert(false); - return NULL; -} - uint32_t space_size(struct space *space) { return space_index(space, 0)->size(); } -/** - * Secondary indexes are built in bulk after all data is - * recovered. This function enables secondary keys on a space. - * Data dictionary spaces are an exception, they are fully - * built right from the start. - */ -void -space_build_secondary_keys(struct space *space) -{ - if (space->index_id_max > 0) { - Index *pk = space->index[0]; - uint32_t n_tuples = pk->size(); - - if (n_tuples > 0) { - say_info("Building secondary indexes in space '%s'...", - space_name(space)); - } - - for (uint32_t j = 1; j < space->index_count; j++) - index_build(space->index[j], pk); - - if (n_tuples > 0) { - say_info("Space '%s': done", space_name(space)); - } - } - engine_recovery *r = &space->handler->recovery; - r->state = READY_ALL_KEYS; - r->recover = space_noop; /* mark the end of recover */ - r->replace = space_replace_all_keys; -} - -/** Build the primary key after loading data from a snapshot. */ -void -space_end_build_primary_key(struct space *space) -{ - space->index[0]->endBuild(); - engine_recovery *r = &space->handler->recovery; - r->state = READY_PRIMARY_KEY; - r->replace = space_replace_primary_key; - r->recover = space_build_secondary_keys; -} - -/** Prepare the primary key for bulk load (loading from - * a snapshot). - */ -void -space_begin_build_primary_key(struct space *space) -{ - space->index[0]->beginBuild(); - engine_recovery *r = &space->handler->recovery; - r->replace = space_replace_build_next; - r->recover = space_end_build_primary_key; -} - -/** - * Bring a space up to speed if its primary key is added during - * XLOG recovery. This is a recovery function called on - * spaces which had no primary key at the end of snapshot - * recovery, and got one only when reading an XLOG. - */ -void -space_build_primary_key(struct space *space) -{ - space_begin_build_primary_key(space); - space_end_build_primary_key(space); -} - -/** Bring a space up to speed once it's got a primary key. - * - * This is a recovery function used for all spaces added after the - * end of SNAP/XLOG recovery. - */ -void -space_build_all_keys(struct space *space) -{ - space_build_primary_key(space); - space_build_secondary_keys(space); -} - void space_validate_tuple(struct space *sp, struct tuple *new_tuple) { diff --git a/src/box/space.h b/src/box/space.h index 3d5190495c48ae22f939e5af1067badd5b51f16b..dd513be6259cacaaaf0d6f2b00f92a1b8be20fc4 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -195,16 +195,6 @@ space_replace(struct space *space, struct tuple *old_tuple, return space->handler->replace(space, old_tuple, new_tuple, mode); } -struct tuple * -space_replace_no_keys(struct space*, struct tuple*, struct tuple*, - enum dup_replace_mode); -struct tuple * -space_replace_primary_key(struct space*, struct tuple*, struct tuple*, - enum dup_replace_mode); - -void space_begin_build_primary_key(struct space *space); -void space_build_primary_key(struct space *space); -void space_build_all_keys(struct space *space); void space_noop(struct space *space); uint32_t diff --git a/src/box/txn.cc b/src/box/txn.cc index 6d23a89b08c7c58f9264a65a6050b070dc5f4248..3d4ae07f2d6d4cf15cf84c081c172e5f9ebb9cb7 100644 --- a/src/box/txn.cc +++ b/src/box/txn.cc @@ -144,6 +144,7 @@ txn_begin(bool autocommit) rlist_nil, txn_on_yield_or_stop, NULL, NULL }; txn->autocommit = autocommit; + txn->engine_tx = NULL; fiber_set_txn(fiber(), txn); return txn; } @@ -207,6 +208,14 @@ txn_commit(struct txn *txn) struct txn_stmt *stmt; /* if (!txn->autocommit && txn->n_stmts && engine_no_yield(txn->engine)) */ + /* xxx: temporary workaround to handle transaction + * conflicts with sophia. + */ + if (txn->engine) { + txn->engine->commit(txn); + txn->engine_tx = NULL; + } + trigger_clear(&txn->fiber_on_yield); trigger_clear(&txn->fiber_on_stop); @@ -228,8 +237,10 @@ txn_commit(struct txn *txn) tnt_raise(LoggedError, ER_WAL_IO); txn->signature = res; } + /* if (txn->engine) txn->engine->commit(txn); + */ trigger_run(&txn->on_commit, txn); /* must not throw. */ } diff --git a/src/ffisyms.cc b/src/ffisyms.cc index e7dafaf0056eef6e83101b94257385dd6a56eb78..b2917b1798336b13b4b11adefc4eef69d4aa14ca 100644 --- a/src/ffisyms.cc +++ b/src/ffisyms.cc @@ -1,3 +1,4 @@ + #include <bit/bit.h> #include <lib/msgpuck/msgpuck.h> #include "scramble.h" diff --git a/test/box/admin.result b/test/box/admin.result index 807cff8b1fdc2191de54ab3bfe0745a8b11243df..973347aa1bf609cbebd3b827727fad26fa23c4a6 100644 --- a/test/box/admin.result +++ b/test/box/admin.result @@ -26,9 +26,10 @@ box.cfg slab_alloc_arena: 0.1 sophia: page_size: 131072 + memory_limit: 0 threads: 5 node_size: 134217728 - memory_limit: 0 + compression: none listen: <uri> logger_nonblock: true snap_dir: . diff --git a/test/box/cfg.result b/test/box/cfg.result index 61e12fb73d441be2a8f7e3918be35412a7201ef0..fc615be3ed65375fb4c314c964cfb8460d8efa40 100644 --- a/test/box/cfg.result +++ b/test/box/cfg.result @@ -2,7 +2,7 @@ --# push filter 'admin: .*' to 'admin: <uri>' box.cfg.nosuchoption = 1 --- -- error: '[string "-- load_cfg.lua - internal file..."]:260: Attempt to modify a read-only +- error: '[string "-- load_cfg.lua - internal file..."]:262: Attempt to modify a read-only table' ... t = {} for k,v in pairs(box.cfg) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t, k..': '..tostring(v)) end end @@ -36,7 +36,7 @@ t -- must be read-only box.cfg() --- -- error: '[string "-- load_cfg.lua - internal file..."]:206: bad argument #1 to ''pairs'' +- error: '[string "-- load_cfg.lua - internal file..."]:208: bad argument #1 to ''pairs'' (table expected, got nil)' ... t = {} for k,v in pairs(box.cfg) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t, k..': '..tostring(v)) end end @@ -70,23 +70,23 @@ t -- check that cfg with unexpected parameter fails. box.cfg{sherlock = 'holmes'} --- -- error: '[string "-- load_cfg.lua - internal file..."]:162: Error: cfg parameter +- error: '[string "-- load_cfg.lua - internal file..."]:164: Error: cfg parameter ''sherlock'' is unexpected' ... -- check that cfg with unexpected type of parameter failes box.cfg{listen = {}} --- -- error: '[string "-- load_cfg.lua - internal file..."]:182: Error: cfg parameter +- error: '[string "-- load_cfg.lua - internal file..."]:184: Error: cfg parameter ''listen'' should be one of types: string, number' ... box.cfg{wal_dir = 0} --- -- error: '[string "-- load_cfg.lua - internal file..."]:176: Error: cfg parameter +- error: '[string "-- load_cfg.lua - internal file..."]:178: Error: cfg parameter ''wal_dir'' should be of type string' ... box.cfg{coredump = 'true'} --- -- error: '[string "-- load_cfg.lua - internal file..."]:176: Error: cfg parameter +- error: '[string "-- load_cfg.lua - internal file..."]:178: Error: cfg parameter ''coredump'' should be of type boolean' ... -------------------------------------------------------------------------------- @@ -94,17 +94,17 @@ box.cfg{coredump = 'true'} -------------------------------------------------------------------------------- box.cfg{slab_alloc_arena = "100500"} --- -- error: '[string "-- load_cfg.lua - internal file..."]:176: Error: cfg parameter +- error: '[string "-- load_cfg.lua - internal file..."]:178: Error: cfg parameter ''slab_alloc_arena'' should be of type number' ... box.cfg{sophia = "sophia"} --- -- error: '[string "-- load_cfg.lua - internal file..."]:170: Error: cfg parameter +- error: '[string "-- load_cfg.lua - internal file..."]:172: Error: cfg parameter ''sophia'' should be a table' ... box.cfg{sophia = {threads = "threads"}} --- -- error: '[string "-- load_cfg.lua - internal file..."]:176: Error: cfg parameter +- error: '[string "-- load_cfg.lua - internal file..."]:178: Error: cfg parameter ''sophia.threads'' should be of type number' ... -------------------------------------------------------------------------------- diff --git a/test/replication/sophia_join.result b/test/replication/sophia_join.result new file mode 100644 index 0000000000000000000000000000000000000000..ece1d73df3c21f82774c65f606769df2cabd4b6f --- /dev/null +++ b/test/replication/sophia_join.result @@ -0,0 +1,162 @@ +box.schema.user.grant('guest', 'replication') +--- +... +space = box.schema.space.create('test', { id = 99999, engine = "sophia" }) +--- +... +index = space:create_index('primary', { type = 'tree'}) +--- +... +for k = 1, 123 do space:insert{k, k*k} end +--- +... +box.snapshot() +--- +- ok +... +------------------------------------------------------------- +replica JOIN +------------------------------------------------------------- +box.space.test:select() +--- +- - [1, 1] + - [2, 4] + - [3, 9] + - [4, 16] + - [5, 25] + - [6, 36] + - [7, 49] + - [8, 64] + - [9, 81] + - [10, 100] + - [11, 121] + - [12, 144] + - [13, 169] + - [14, 196] + - [15, 225] + - [16, 256] + - [17, 289] + - [18, 324] + - [19, 361] + - [20, 400] + - [21, 441] + - [22, 484] + - [23, 529] + - [24, 576] + - [25, 625] + - [26, 676] + - [27, 729] + - [28, 784] + - [29, 841] + - [30, 900] + - [31, 961] + - [32, 1024] + - [33, 1089] + - [34, 1156] + - [35, 1225] + - [36, 1296] + - [37, 1369] + - [38, 1444] + - [39, 1521] + - [40, 1600] + - [41, 1681] + - [42, 1764] + - [43, 1849] + - [44, 1936] + - [45, 2025] + - [46, 2116] + - [47, 2209] + - [48, 2304] + - [49, 2401] + - [50, 2500] + - [51, 2601] + - [52, 2704] + - [53, 2809] + - [54, 2916] + - [55, 3025] + - [56, 3136] + - [57, 3249] + - [58, 3364] + - [59, 3481] + - [60, 3600] + - [61, 3721] + - [62, 3844] + - [63, 3969] + - [64, 4096] + - [65, 4225] + - [66, 4356] + - [67, 4489] + - [68, 4624] + - [69, 4761] + - [70, 4900] + - [71, 5041] + - [72, 5184] + - [73, 5329] + - [74, 5476] + - [75, 5625] + - [76, 5776] + - [77, 5929] + - [78, 6084] + - [79, 6241] + - [80, 6400] + - [81, 6561] + - [82, 6724] + - [83, 6889] + - [84, 7056] + - [85, 7225] + - [86, 7396] + - [87, 7569] + - [88, 7744] + - [89, 7921] + - [90, 8100] + - [91, 8281] + - [92, 8464] + - [93, 8649] + - [94, 8836] + - [95, 9025] + - [96, 9216] + - [97, 9409] + - [98, 9604] + - [99, 9801] + - [100, 10000] + - [101, 10201] + - [102, 10404] + - [103, 10609] + - [104, 10816] + - [105, 11025] + - [106, 11236] + - [107, 11449] + - [108, 11664] + - [109, 11881] + - [110, 12100] + - [111, 12321] + - [112, 12544] + - [113, 12769] + - [114, 12996] + - [115, 13225] + - [116, 13456] + - [117, 13689] + - [118, 13924] + - [119, 14161] + - [120, 14400] + - [121, 14641] + - [122, 14884] + - [123, 15129] +... +space:drop() +--- +... +box.snapshot() +--- +- ok +... +ffi = require('ffi') +--- +... +ffi.cdef("int sophia_schedule(void);") +--- +... +ffi.C.sophia_schedule() >= 0 +--- +- true +... diff --git a/test/replication/sophia_join.test.py b/test/replication/sophia_join.test.py new file mode 100644 index 0000000000000000000000000000000000000000..945c8e397ae205305ecddf2ea1d89ebab3499cad --- /dev/null +++ b/test/replication/sophia_join.test.py @@ -0,0 +1,37 @@ +import os +import glob +from lib.tarantool_server import TarantoolServer + +# master server +master = server +master_id = master.get_param('server')['id'] + +master.admin("box.schema.user.grant('guest', 'replication')") +master.admin("space = box.schema.space.create('test', { id = 99999, engine = \"sophia\" })") +master.admin("index = space:create_index('primary', { type = 'tree'})") +master.admin('for k = 1, 123 do space:insert{k, k*k} end') +master.admin('box.snapshot()') +lsn = master.get_lsn(master_id) + +print '-------------------------------------------------------------' +print 'replica JOIN' +print '-------------------------------------------------------------' + +# replica server +replica = TarantoolServer(server.ini) +replica.script = 'replication/replica.lua' +replica.vardir = os.path.join(server.vardir, 'replica') +replica.rpl_master = master +replica.deploy() +replica.wait_lsn(master_id, lsn) +replica.admin('box.space.test:select()') + +replica.stop() +replica.cleanup(True) + +# remove space +master.admin("space:drop()") +master.admin('box.snapshot()') +master.admin("ffi = require('ffi')") +master.admin("ffi.cdef(\"int sophia_schedule(void);\")") +master.admin("ffi.C.sophia_schedule() >= 0") diff --git a/test/sophia/crud.result b/test/sophia/crud.result index 550a79d18992dd1303493b78461cc48a3741a87a..4d43a559a8922a8fd4ff75d61ba44e67fa0712d6 100644 --- a/test/sophia/crud.result +++ b/test/sophia/crud.result @@ -1171,7 +1171,6 @@ space:drop() ... sophia_schedule() --- -- 1 ... sophia_dir()[1] --- diff --git a/test/sophia/dml.result b/test/sophia/ddl.result similarity index 98% rename from test/sophia/dml.result rename to test/sophia/ddl.result index 345a84d22b2b1c33937a5e391c60911dc0d7d66b..3c1c14425b2b6bb4d429d13d43dc01ce0fc2e721 100644 --- a/test/sophia/dml.result +++ b/test/sophia/ddl.result @@ -11,7 +11,6 @@ space:drop() ... sophia_schedule() --- -- 0 ... sophia_dir()[1] --- @@ -33,7 +32,6 @@ space:drop() ... sophia_schedule() --- -- 1 ... sophia_dir()[1] --- @@ -61,7 +59,6 @@ space:drop() ... sophia_schedule() --- -- 1 ... sophia_dir()[1] --- @@ -83,7 +80,6 @@ space:drop() ... sophia_schedule() --- -- 1 ... sophia_dir()[1] --- @@ -105,7 +101,6 @@ space:drop() ... sophia_schedule() --- -- 1 ... sophia_dir()[1] --- @@ -124,7 +119,6 @@ space:drop() ... sophia_schedule() --- -- 0 ... sophia_dir()[1] --- @@ -147,7 +141,6 @@ space:drop() ... sophia_schedule() --- -- 1 ... sophia_dir()[1] --- @@ -188,5 +181,4 @@ space:drop() ... sophia_schedule() --- -- 1 ... diff --git a/test/sophia/dml.test.lua b/test/sophia/ddl.test.lua similarity index 100% rename from test/sophia/dml.test.lua rename to test/sophia/ddl.test.lua diff --git a/test/sophia/gh.result b/test/sophia/gh.result index c1933e0429adef4d27af6c2b7820e35a6c5cf835..cbe6fa5d15900340ae29338bb207f548c769cc54 100644 --- a/test/sophia/gh.result +++ b/test/sophia/gh.result @@ -14,7 +14,6 @@ s:drop() ... sophia_schedule() --- -- 1 ... s = box.schema.space.create('space0', {engine='sophia'}) --- @@ -38,7 +37,6 @@ s:drop() ... sophia_schedule() --- -- 1 ... s = box.schema.space.create('space0', {engine='sophia'}) --- @@ -62,7 +60,6 @@ s:drop() ... sophia_schedule() --- -- 1 ... -- gh-280: Sophia: crash if insert without index s = box.schema.space.create('test', {engine='sophia'}) @@ -77,7 +74,6 @@ s:drop() ... sophia_schedule() --- -- 0 ... -- gh-436: No error when creating temporary sophia space s = box.schema.space.create('tester',{engine='sophia', temporary=true}) @@ -114,7 +110,6 @@ s:drop() ... sophia_schedule() --- -- 1 ... s = box.schema.space.create('tester', {engine='sophia'}) --- @@ -144,7 +139,6 @@ s:drop() ... sophia_schedule() --- -- 1 ... -- gh-680: Sophia: assertion on update s = box.schema.space.create('tester', {engine='sophia'}) @@ -186,5 +180,4 @@ s:drop() ... sophia_schedule() --- -- 1 ... diff --git a/test/sophia/options.result b/test/sophia/options.result index 75c23ee4a8d5f129793fca2178484888994edce6..4249fd3862eb027730828d2108c946b35d499774 100644 --- a/test/sophia/options.result +++ b/test/sophia/options.result @@ -1,9 +1,10 @@ box.cfg.sophia --- - page_size: 131072 + memory_limit: 0 threads: 0 node_size: 134217728 - memory_limit: 0 + compression: none ... box.cfg.sophia.threads = 3 --- diff --git a/test/sophia/random.result b/test/sophia/random.result index 4d57fe8919eb99c46cad2032c2f1c1a8071e073f..56b896d871e1528ac386599c353ce860786ee534 100644 --- a/test/sophia/random.result +++ b/test/sophia/random.result @@ -17,5 +17,4 @@ space:drop() ... sophia_schedule() --- -- 1 ... diff --git a/test/sophia/snapshot.result b/test/sophia/snapshot.result index 6b27014b729998fbf0388c4f4d2dbe53f37ee17b..06454b80527b1a59cf63047cbc009b9a130014ce 100644 --- a/test/sophia/snapshot.result +++ b/test/sophia/snapshot.result @@ -1,15 +1,23 @@ -- snapshot -space = box.schema.space.create('test', { id = 100, engine = 'sophia' }) +os.execute("rm -f *.snap") --- +- 0 ... -index = space:create_index('primary') +os.execute("rm -f *.xlog") +--- +- 0 +... +os.execute("touch mt") +--- +- 0 +... +--# stop server default +--# start server default +space = box.schema.space.create('test', { engine = 'sophia' }) --- ... -sophia_printdir() +index = space:create_index('primary') --- -- '100 - -' ... for key = 1, 351 do space:insert({key}) end --- @@ -18,6 +26,10 @@ box.snapshot() --- - ok ... +os.execute("rm -f mt") +--- +- 0 +... os.execute("touch lock") --- - 0 diff --git a/test/sophia/snapshot.test.lua b/test/sophia/snapshot.test.lua index a213e2fb16698b816b05d9bd850b5170eb30c8c8..6b3f0743d72323834fb86b74a836767944468115 100644 --- a/test/sophia/snapshot.test.lua +++ b/test/sophia/snapshot.test.lua @@ -1,6 +1,8 @@ -- snapshot +os.execute("rm -f *.snap") +os.execute("rm -f *.xlog") os.execute("touch mt") --# stop server default diff --git a/test/sophia/snapshot_gc.result b/test/sophia/snapshot_gc.result new file mode 100644 index 0000000000000000000000000000000000000000..6bd29f8710f6382d57e4ef881be8c10bda99f76a --- /dev/null +++ b/test/sophia/snapshot_gc.result @@ -0,0 +1,110 @@ +os.execute("rm -f *.snap") +--- +- 0 +... +os.execute("rm -f *.xlog") +--- +- 0 +... +os.execute("touch mt") +--- +- 0 +... +--# stop server default +--# start server default +space = box.schema.create_space('test', { engine = 'sophia' }) +--- +... +index = space:create_index('primary') +--- +... +for key = 1, 351 do space:insert({key}) end +--- +... +box.snapshot() +--- +- ok +... +space:drop() +--- +... +sophia_schedule() +--- +... +sophia_dir()[1] +--- +- 1 +... +-- ensure that previous space has been garbage collected +space = box.schema.create_space('test', { engine = 'sophia' }) +--- +... +index = space:create_index('primary') +--- +... +for key = 1, 351 do space:insert({key}) end +--- +... +sophia_dir()[1] -- 2 +--- +- 2 +... +box.snapshot() +--- +- ok +... +space:drop() +--- +... +sophia_schedule() +--- +... +sophia_dir()[1] -- 1 +--- +- 1 +... +space = box.schema.create_space('test', { engine = 'sophia' }) +--- +... +index = space:create_index('primary') +--- +... +for key = 1, 351 do space:insert({key}) end +--- +... +sophia_dir()[1] -- 2 +--- +- 2 +... +box.snapshot() +--- +- ok +... +space:drop() +--- +... +sophia_schedule() +--- +... +sophia_dir()[1] -- 1 +--- +- 1 +... +os.execute("rm -f *.snap") +--- +- 0 +... +os.execute("rm -f *.xlog") +--- +- 0 +... +os.execute("rm -f mt") +--- +- 0 +... +os.execute("rm -f lock") +--- +- 0 +... +--# stop server default +--# start server default diff --git a/test/sophia/snapshot_gc.test.lua b/test/sophia/snapshot_gc.test.lua new file mode 100644 index 0000000000000000000000000000000000000000..52b372c32063a7efbe041433df996b9a6243412d --- /dev/null +++ b/test/sophia/snapshot_gc.test.lua @@ -0,0 +1,43 @@ + +os.execute("rm -f *.snap") +os.execute("rm -f *.xlog") +os.execute("touch mt") + +--# stop server default +--# start server default + +space = box.schema.create_space('test', { engine = 'sophia' }) +index = space:create_index('primary') + +for key = 1, 351 do space:insert({key}) end +box.snapshot() +space:drop() +sophia_schedule() +sophia_dir()[1] + +-- ensure that previous space has been garbage collected +space = box.schema.create_space('test', { engine = 'sophia' }) +index = space:create_index('primary') +for key = 1, 351 do space:insert({key}) end +sophia_dir()[1] -- 2 +box.snapshot() +space:drop() +sophia_schedule() +sophia_dir()[1] -- 1 + +space = box.schema.create_space('test', { engine = 'sophia' }) +index = space:create_index('primary') +for key = 1, 351 do space:insert({key}) end +sophia_dir()[1] -- 2 +box.snapshot() +space:drop() +sophia_schedule() +sophia_dir()[1] -- 1 + +os.execute("rm -f *.snap") +os.execute("rm -f *.xlog") +os.execute("rm -f mt") +os.execute("rm -f lock") + +--# stop server default +--# start server default diff --git a/test/sophia/snapshot_view.result b/test/sophia/snapshot_view.result new file mode 100644 index 0000000000000000000000000000000000000000..e52947f1a5e31d9060d283f59708acd998a51929 --- /dev/null +++ b/test/sophia/snapshot_view.result @@ -0,0 +1,83 @@ +os.execute("rm -f *.snap") +--- +- 0 +... +os.execute("rm -f *.xlog") +--- +- 0 +... +os.execute("touch mt") +--- +- 0 +... +--# stop server default +--# start server default +space = box.schema.create_space('test', { engine = 'sophia' }) +--- +... +index = space:create_index('primary') +--- +... +for key = 1, 351 do space:insert({key}) end +--- +... +box.snapshot() +--- +- ok +... +space:drop() +--- +... +sophia_schedule() +--- +... +-- remove tarantool xlogs +os.execute("rm -f *.xlog") +--- +- 0 +... +os.execute("rm -f mt") +--- +- 0 +... +os.execute("touch lock") +--- +- 0 +... +--# stop server default +--# start server default +space = box.space['test'] +--- +... +space:len() +--- +- 351 +... +sophia_dir()[1] +--- +- 1 +... +space:drop() +--- +... +sophia_schedule() +--- +... +sophia_dir()[1] +--- +- 1 +... +os.execute("rm -f *.snap") +--- +- 0 +... +os.execute("rm -f *.xlog") +--- +- 0 +... +os.execute("rm -f lock") +--- +- 0 +... +--# stop server default +--# start server default diff --git a/test/sophia/snapshot_view.test.lua b/test/sophia/snapshot_view.test.lua new file mode 100644 index 0000000000000000000000000000000000000000..f0d6ce5f5eec5d782541831f818d1bbd230745e5 --- /dev/null +++ b/test/sophia/snapshot_view.test.lua @@ -0,0 +1,38 @@ + +os.execute("rm -f *.snap") +os.execute("rm -f *.xlog") +os.execute("touch mt") + +--# stop server default +--# start server default + +space = box.schema.create_space('test', { engine = 'sophia' }) +index = space:create_index('primary') + +for key = 1, 351 do space:insert({key}) end +box.snapshot() +space:drop() +sophia_schedule() + +-- remove tarantool xlogs +os.execute("rm -f *.xlog") + +os.execute("rm -f mt") +os.execute("touch lock") + +--# stop server default +--# start server default + +space = box.space['test'] +space:len() +sophia_dir()[1] +space:drop() +sophia_schedule() +sophia_dir()[1] + +os.execute("rm -f *.snap") +os.execute("rm -f *.xlog") +os.execute("rm -f lock") + +--# stop server default +--# start server default diff --git a/test/sophia/suite.ini b/test/sophia/suite.ini index 121dcab9d4bd5155925e5519c3f957da1afb4b17..368cc90bd4702f4394f53b70546c3861d752913e 100644 --- a/test/sophia/suite.ini +++ b/test/sophia/suite.ini @@ -2,7 +2,7 @@ core = tarantool description = sophia integration tests script = box.lua -disabled = info.test.lua truncate.test.lua snapshot.test.lua +disabled = info.test.lua truncate.test.lua valgrind_disabled = release_disabled = lua_libs = suite.lua index_random_test.lua diff --git a/test/sophia/suite.lua b/test/sophia/suite.lua index 1138205d07eacfce8909641ef40acacec43eb13c..47bc3af6d1d4af73064fa1a67ba6f4d161a5a9cf 100644 --- a/test/sophia/suite.lua +++ b/test/sophia/suite.lua @@ -8,7 +8,7 @@ ffi.cdef[[ ]] function sophia_schedule() - return ffi.C.sophia_schedule() + ffi.C.sophia_schedule() end function sophia_dir() diff --git a/test/sophia/transaction_multidb.result b/test/sophia/transaction_multidb.result index 8131471ec10a01cc0907f7d296dcc7a964438503..42cab9b94b551be306a2ed9d02570138dfee5e84 100644 --- a/test/sophia/transaction_multidb.result +++ b/test/sophia/transaction_multidb.result @@ -173,12 +173,10 @@ a:drop() ... sophia_schedule() --- -- 1 ... b:drop() --- ... sophia_schedule() --- -- 1 ... diff --git a/third_party/sophia b/third_party/sophia index ee54a7c08c2a87bf996c7a3f014b0d01dd4bb1e2..68ce375fb75f9194edf967d96d0ff5dc04f3724f 160000 --- a/third_party/sophia +++ b/third_party/sophia @@ -1 +1 @@ -Subproject commit ee54a7c08c2a87bf996c7a3f014b0d01dd4bb1e2 +Subproject commit 68ce375fb75f9194edf967d96d0ff5dc04f3724f