Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH 0/2] Box control event trigger
@ 2018-08-28 16:19 Georgy Kirichenko
  2018-08-28 16:19 ` [tarantool-patches] [PATCH 1/2] Update lua space cache just after creation Georgy Kirichenko
  2018-08-28 16:19 ` [tarantool-patches] [PATCH 2/2] On ctl event trigger Georgy Kirichenko
  0 siblings, 2 replies; 15+ messages in thread
From: Georgy Kirichenko @ 2018-08-28 16:19 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Georgy Kirichenko

Introduce a ctl event trigger fired in cases of a bootstrap/recovery status
changes, a space create/alter/drop action, an applier state change and
shutdown. Trigger could be set with box.ctl_event even before the first
box.cfg invocation to control recovery and bootstrap behavior.
The patchset contains two patches, the first fixes a lua space cache
updates, the second introduces the control trigger.


Georgy Kirichenko (2):
  Update lua space cache just after creation
  On ctl event trigger

 src/box/CMakeLists.txt        |   2 +
 src/box/alter.cc              |  46 +++--
 src/box/applier.cc            |   6 +
 src/box/box.cc                |  42 ++++
 src/box/lua/init.c            |   2 +
 src/box/lua/load_cfg.lua      |   1 +
 test/box/ctl_event.result     | 364 ++++++++++++++++++++++++++++++++++
 test/box/ctl_event.test.lua   |  76 +++++++
 test/box/errinj.result        |  61 ++++++
 test/box/errinj.test.lua      |  27 +++
 test/box/lua/trig_master.lua  |   8 +
 test/box/lua/trig_replica.lua |  46 +++++
 test/box/misc.result          |  73 +++++++
 test/box/misc.test.lua        |  34 ++++
 14 files changed, 769 insertions(+), 19 deletions(-)
 create mode 100644 test/box/ctl_event.result
 create mode 100644 test/box/ctl_event.test.lua
 create mode 100644 test/box/lua/trig_master.lua
 create mode 100644 test/box/lua/trig_replica.lua

-- 
2.18.0
https://github.com/tarantool/tarantool/issues/3159
https://github.com/tarantool/tarantool/tree/g.kirichenko/gh-3159-on-ctl-event-trigger

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [tarantool-patches] [PATCH 1/2] Update lua space cache just after creation
  2018-08-28 16:19 [tarantool-patches] [PATCH 0/2] Box control event trigger Georgy Kirichenko
@ 2018-08-28 16:19 ` Georgy Kirichenko
  2018-08-30 12:06   ` Vladimir Davydov
  2018-08-30 12:31   ` Konstantin Osipov
  2018-08-28 16:19 ` [tarantool-patches] [PATCH 2/2] On ctl event trigger Georgy Kirichenko
  1 sibling, 2 replies; 15+ messages in thread
From: Georgy Kirichenko @ 2018-08-28 16:19 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Georgy Kirichenko

The lua space cache (box.space.*) should be valid just after space is
created because space is ready to accept new records or does not exists
before wal would be written. So invoke a space create/drop trigger after
a space is changed and recall it in a case of rollback.

Relates: 3159
---
 src/box/alter.cc         | 30 ++++++-----------
 test/box/errinj.result   | 61 ++++++++++++++++++++++++++++++++++
 test/box/errinj.test.lua | 27 +++++++++++++++
 test/box/misc.result     | 72 ++++++++++++++++++++++++++++++++++++++++
 test/box/misc.test.lua   | 34 +++++++++++++++++++
 5 files changed, 205 insertions(+), 19 deletions(-)

diff --git a/src/box/alter.cc b/src/box/alter.cc
index a6299a12e..b2758a4d9 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -787,7 +787,6 @@ alter_space_commit(struct trigger *trigger, void *event)
 		op->commit(alter, txn->signature);
 	}
 
-	trigger_run_xc(&on_alter_space, alter->new_space);
 
 	alter->new_space = NULL; /* for alter_space_delete(). */
 	/*
@@ -825,6 +824,7 @@ alter_space_rollback(struct trigger *trigger, void * /* event */)
 	struct space *new_space = space_cache_replace(alter->old_space);
 	assert(new_space == alter->new_space);
 	(void) new_space;
+	trigger_run_xc(&on_alter_space, alter->old_space);
 	alter_space_delete(alter);
 }
 
@@ -1417,7 +1417,6 @@ on_drop_space_commit(struct trigger *trigger, void *event)
 {
 	(void) event;
 	struct space *space = (struct space *)trigger->data;
-	trigger_run_xc(&on_alter_space, space);
 	space_delete(space);
 }
 
@@ -1432,16 +1431,6 @@ on_drop_space_rollback(struct trigger *trigger, void *event)
 	(void) event;
 	struct space *space = (struct space *)trigger->data;
 	space_cache_replace(space);
-}
-
-/**
- * Run the triggers registered on commit of a change in _space.
- */
-static void
-on_create_space_commit(struct trigger *trigger, void *event)
-{
-	(void) event;
-	struct space *space = (struct space *)trigger->data;
 	trigger_run_xc(&on_alter_space, space);
 }
 
@@ -1460,6 +1449,7 @@ on_create_space_rollback(struct trigger *trigger, void *event)
 	struct space *cached = space_cache_delete(space_id(space));
 	(void) cached;
 	assert(cached == space);
+	trigger_run_xc(&on_alter_space, space);
 	space_delete(space);
 }
 
@@ -1626,12 +1616,10 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		 * so it's safe to simply drop the space on
 		 * rollback.
 		 */
-		struct trigger *on_commit =
-			txn_alter_trigger_new(on_create_space_commit, space);
-		txn_on_commit(txn, on_commit);
 		struct trigger *on_rollback =
 			txn_alter_trigger_new(on_create_space_rollback, space);
 		txn_on_rollback(txn, on_rollback);
+		trigger_run_xc(&on_alter_space, space);
 	} else if (new_tuple == NULL) { /* DELETE */
 		access_check_ddl(old_space->def->name, old_space->def->id,
 				 old_space->def->uid, SC_SPACE, PRIV_D, true);
@@ -1674,6 +1662,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		struct trigger *on_rollback =
 			txn_alter_trigger_new(on_drop_space_rollback, space);
 		txn_on_rollback(txn, on_rollback);
+		trigger_run_xc(&on_alter_space, old_space);
 	} else { /* UPDATE, REPLACE */
 		assert(old_space != NULL && new_tuple != NULL);
 		struct space_def *def =
@@ -1730,6 +1719,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		(void) new UpdateSchemaVersion(alter);
 		alter_space_do(txn, alter);
 		alter_guard.is_active = false;
+		trigger_run_xc(&on_alter_space, alter->new_space);
 	}
 }
 
@@ -1931,6 +1921,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 	(void) new UpdateSchemaVersion(alter);
 	alter_space_do(txn, alter);
 	scoped_guard.is_active = false;
+	trigger_run_xc(&on_alter_space, alter->new_space);
 }
 
 /**
@@ -3180,7 +3171,7 @@ on_replace_dd_sequence_data(struct trigger * /* trigger */, void *event)
  * Run the triggers registered on commit of a change in _space.
  */
 static void
-on_commit_dd_space_sequence(struct trigger *trigger, void * /* event */)
+on_rollback_dd_space_sequence(struct trigger *trigger, void * /* event */)
 {
 	struct space *space = (struct space *) trigger->data;
 	trigger_run_xc(&on_alter_space, space);
@@ -3230,9 +3221,9 @@ on_replace_dd_space_sequence(struct trigger * /* trigger */, void *event)
 	access_check_ddl(space->def->name, space->def->id, space->def->uid,
 			 SC_SPACE, PRIV_A, false);
 
-	struct trigger *on_commit =
-		txn_alter_trigger_new(on_commit_dd_space_sequence, space);
-	txn_on_commit(txn, on_commit);
+	struct trigger *on_rollback =
+		txn_alter_trigger_new(on_rollback_dd_space_sequence, space);
+	txn_on_rollback(txn, on_rollback);
 
 	if (stmt->new_tuple != NULL) {			/* INSERT, UPDATE */
 		struct index *pk = index_find_xc(space, 0);
@@ -3248,6 +3239,7 @@ on_replace_dd_space_sequence(struct trigger * /* trigger */, void *event)
 		assert(space->sequence == seq);
 		space->sequence = NULL;
 	}
+	trigger_run_xc(&on_alter_space, space);
 }
 
 /* }}} sequence */
diff --git a/test/box/errinj.result b/test/box/errinj.result
index c7e4ce20b..1fe2c0c44 100644
--- a/test/box/errinj.result
+++ b/test/box/errinj.result
@@ -1571,3 +1571,64 @@ fio = require('fio')
 box.space.test:drop()
 ---
 ...
+-- allocate a space id to prevent max space id update
+trig = box.schema.space.create('trig')
+---
+...
+trig_id = trig.id
+---
+...
+trig:drop()
+---
+...
+trig = nil
+---
+...
+fiber = require('fiber')
+---
+...
+ch = fiber.channel(1)
+---
+...
+errinj = box.error.injection
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+-- check space exists just after creation
+errinj.set("ERRINJ_WAL_WRITE", true);
+---
+- ok
+...
+_ = fiber.create(function ()
+        fiber.create(function ()
+            pcall(box.schema.space.create, 'trig', {id = trig_id})
+            ch:put(true)
+        end)
+        trig = box.space.trig
+    end);
+---
+...
+trig ~= nil;
+---
+- true
+...
+ch:get();
+---
+- true
+...
+--and not exists after rollback
+box.space.trig;
+---
+- null
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+errinj.set("ERRINJ_WAL_WRITE", false)
+---
+- ok
+...
diff --git a/test/box/errinj.test.lua b/test/box/errinj.test.lua
index a3ea659aa..32569f605 100644
--- a/test/box/errinj.test.lua
+++ b/test/box/errinj.test.lua
@@ -557,3 +557,30 @@ fio = require('fio')
 #fio.glob(fio.pathjoin(box.cfg.vinyl_dir, box.space.test.id, 0, '*.index.inprogress')) == 0
 
 box.space.test:drop()
+
+
+-- allocate a space id to prevent max space id update
+trig = box.schema.space.create('trig')
+trig_id = trig.id
+trig:drop()
+trig = nil
+fiber = require('fiber')
+ch = fiber.channel(1)
+errinj = box.error.injection
+test_run:cmd("setopt delimiter ';'")
+-- check space exists just after creation
+errinj.set("ERRINJ_WAL_WRITE", true);
+_ = fiber.create(function ()
+        fiber.create(function ()
+            pcall(box.schema.space.create, 'trig', {id = trig_id})
+            ch:put(true)
+        end)
+        trig = box.space.trig
+    end);
+trig ~= nil;
+ch:get();
+--and not exists after rollback
+box.space.trig;
+test_run:cmd("setopt delimiter ''");
+
+errinj.set("ERRINJ_WAL_WRITE", false)
diff --git a/test/box/misc.result b/test/box/misc.result
index 62376754e..e213d7964 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -1196,3 +1196,75 @@ box.cfg{too_long_threshold = too_long_threshold}
 s:drop()
 ---
 ...
+-- Test if space is visible just after creation
+fiber = require('fiber')
+---
+...
+-- allocate a space id to prevent max space id update
+trig = box.schema.space.create('trig')
+---
+...
+trig_id = trig.id
+---
+...
+trig:drop()
+---
+...
+trig = nil
+---
+...
+ch = fiber.channel(1)
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+-- check space exists just after creation
+_ = fiber.create(function ()
+        fiber.create(function ()
+            box.schema.space.create('trig', {id = trig_id})
+            ch:put(true)
+        end)
+        trig = box.space.trig
+    end);
+---
+...
+trig ~= nil;
+---
+- true
+...
+ch:get();
+---
+- true
+...
+trig == box.space.trig;
+---
+- true
+...
+-- check space does not exists just after deletion
+_ = fiber.create(function ()
+        fiber.create(function ()
+            box.space.trig:drop()
+            ch:put(true)
+        end)
+        trig = box.space.trig
+    end);
+---
+...
+trig == nil;
+---
+- false
+...
+ch:get();
+---
+- true
+...
+box.space.trig;
+---
+- null
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
diff --git a/test/box/misc.test.lua b/test/box/misc.test.lua
index d6815645e..879e77930 100644
--- a/test/box/misc.test.lua
+++ b/test/box/misc.test.lua
@@ -336,3 +336,37 @@ rows == expected_rows
 lsn == expected_lsn
 box.cfg{too_long_threshold = too_long_threshold}
 s:drop()
+
+-- Test if space is visible just after creation
+fiber = require('fiber')
+-- allocate a space id to prevent max space id update
+trig = box.schema.space.create('trig')
+trig_id = trig.id
+trig:drop()
+trig = nil
+ch = fiber.channel(1)
+test_run:cmd("setopt delimiter ';'")
+-- check space exists just after creation
+_ = fiber.create(function ()
+        fiber.create(function ()
+            box.schema.space.create('trig', {id = trig_id})
+            ch:put(true)
+        end)
+        trig = box.space.trig
+    end);
+trig ~= nil;
+ch:get();
+trig == box.space.trig;
+
+-- check space does not exists just after deletion
+_ = fiber.create(function ()
+        fiber.create(function ()
+            box.space.trig:drop()
+            ch:put(true)
+        end)
+        trig = box.space.trig
+    end);
+trig == nil;
+ch:get();
+box.space.trig;
+test_run:cmd("setopt delimiter ''");
-- 
2.18.0

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [tarantool-patches] [PATCH 2/2] On ctl event trigger
  2018-08-28 16:19 [tarantool-patches] [PATCH 0/2] Box control event trigger Georgy Kirichenko
  2018-08-28 16:19 ` [tarantool-patches] [PATCH 1/2] Update lua space cache just after creation Georgy Kirichenko
@ 2018-08-28 16:19 ` Georgy Kirichenko
  2018-08-30 12:07   ` Vladimir Davydov
                     ` (3 more replies)
  1 sibling, 4 replies; 15+ messages in thread
From: Georgy Kirichenko @ 2018-08-28 16:19 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Georgy Kirichenko

Introduce a ctl event trigger fired in cases of a bootstrap/recovery status
changes, a space create/alter/drop action, an applier state change and
shutdown. Trigger could be set with box.ctl_event even before the first
box.cfg invocation to control recovery and bootstrap behavior.

Event constants accessible via box.ctl_event.const()
There are events:
  - RECOVERY
  - SPACE
  - SHUTDOWN
  - APPLIER

A recovery event might have a status:
   * RECOVERY_SNAPSHOT_START
   * RECOVERY_SNAPSHOT_DONE
   * RECOVERY_HOT_STANDBY_START
   * RECOVERY_HOT_STANDBY_DONE
   * RECOVERY_XLOGS_DONE
   * RECOVERY_BOOTSTRAP_START
   * RECOVERY_BOOTSTRAP_DONE
   * RECOVERY_INITIAL_JOIN_START
   * RECOVERY_INITIAL_JOIN_DONE
   * RECOVERY_FINAL_JOIN_DONE

A space event consists of space identifier and action:
   * SPACE_CREATE
   * SPACE_ALTER
   * SPACE_DELETE

An applier event contains peer uuid and state:
   * APPLIER_OFF
   * APPLIER_CONNECT
   * APPLIER_CONNECTED
   * APPLIER_AUTH
   * APPLIER_READY
   * APPLIER_INITIAL_JOIN
   * APPLIER_FINAL_JOIN
   * APPLIER_JOINED
   * APPLIER_SYNC
   * APPLIER_FOLLOW
   * APPLIER_STOPPED
   * APPLIER_DISCONNECTED
   * APPLIER_LOADING

Fixes: #3159
---
 src/box/CMakeLists.txt        |   2 +
 src/box/alter.cc              |  16 ++
 src/box/applier.cc            |   6 +
 src/box/box.cc                |  42 ++++
 src/box/lua/init.c            |   2 +
 src/box/lua/load_cfg.lua      |   1 +
 test/box/ctl_event.result     | 364 ++++++++++++++++++++++++++++++++++
 test/box/ctl_event.test.lua   |  76 +++++++
 test/box/lua/trig_master.lua  |   8 +
 test/box/lua/trig_replica.lua |  46 +++++
 test/box/misc.result          |   1 +
 11 files changed, 564 insertions(+)
 create mode 100644 test/box/ctl_event.result
 create mode 100644 test/box/ctl_event.test.lua
 create mode 100644 test/box/lua/trig_master.lua
 create mode 100644 test/box/lua/trig_replica.lua

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index cab6a2276..ee495a037 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -113,6 +113,7 @@ add_library(box STATIC
     journal.c
     wal.c
     call.c
+    ctl_event.c
     ${lua_sources}
     lua/init.c
     lua/call.c
@@ -131,6 +132,7 @@ add_library(box STATIC
     lua/session.c
     lua/net_box.c
     lua/xlog.c
+    lua/ctl_event.c
     ${bin_sources})
 
 target_link_libraries(box box_error tuple stat xrow xlog vclock crc32 scramble
diff --git a/src/box/alter.cc b/src/box/alter.cc
index b2758a4d9..8e44ada9b 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -52,6 +52,7 @@
 #include "identifier.h"
 #include "version.h"
 #include "sequence.h"
+#include "ctl_event.h"
 
 /**
  * chap-sha1 of empty string, i.e.
@@ -1620,6 +1621,11 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 			txn_alter_trigger_new(on_create_space_rollback, space);
 		txn_on_rollback(txn, on_rollback);
 		trigger_run_xc(&on_alter_space, space);
+		struct on_ctl_event event;
+		event.type = CTL_SPACE;
+		event.space.action = CTL_SPACE_CREATE;
+		event.space.space_id = old_id;
+		trigger_run(&on_ctl_trigger, &event);
 	} else if (new_tuple == NULL) { /* DELETE */
 		access_check_ddl(old_space->def->name, old_space->def->id,
 				 old_space->def->uid, SC_SPACE, PRIV_D, true);
@@ -1663,6 +1669,11 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 			txn_alter_trigger_new(on_drop_space_rollback, space);
 		txn_on_rollback(txn, on_rollback);
 		trigger_run_xc(&on_alter_space, old_space);
+		struct on_ctl_event event;
+		event.type = CTL_SPACE;
+		event.space.action = CTL_SPACE_DELETE;
+		event.space.space_id = old_id;
+		trigger_run(&on_ctl_trigger, &event);
 	} else { /* UPDATE, REPLACE */
 		assert(old_space != NULL && new_tuple != NULL);
 		struct space_def *def =
@@ -1720,6 +1731,11 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		alter_space_do(txn, alter);
 		alter_guard.is_active = false;
 		trigger_run_xc(&on_alter_space, alter->new_space);
+		struct on_ctl_event event;
+		event.type = CTL_SPACE;
+		event.space.action = CTL_SPACE_ALTER;
+		event.space.space_id = old_id;
+		trigger_run(&on_ctl_trigger, &event);
 	}
 }
 
diff --git a/src/box/applier.cc b/src/box/applier.cc
index 28df8f7ca..4a42c6b43 100644
--- a/src/box/applier.cc
+++ b/src/box/applier.cc
@@ -48,6 +48,7 @@
 #include "error.h"
 #include "session.h"
 #include "cfg.h"
+#include "ctl_event.h"
 
 STRS(applier_state, applier_STATE);
 
@@ -58,6 +59,11 @@ applier_set_state(struct applier *applier, enum applier_state state)
 	say_debug("=> %s", applier_state_strs[state] +
 		  strlen("APPLIER_"));
 	trigger_run_xc(&applier->on_state, applier);
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_APPLIER;
+	ctl_event.applier.replica_uuid = applier->uuid;
+	ctl_event.applier.status = state;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 }
 
 /**
diff --git a/src/box/box.cc b/src/box/box.cc
index 0004140e9..01e512cc2 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -73,6 +73,7 @@
 #include "call.h"
 #include "func.h"
 #include "sequence.h"
+#include "ctl_event.h"
 
 static char status[64] = "unknown";
 
@@ -1602,6 +1603,9 @@ box_free(void)
 	 * initialized
 	 */
 	if (is_box_configured) {
+		struct on_ctl_event ctl_event;
+		ctl_event.type = CTL_SHUTDOWN;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 #if 0
 		session_free();
 		user_cache_free();
@@ -1670,6 +1674,11 @@ bootstrap_master(const struct tt_uuid *replicaset_uuid)
 	if (boxk(IPROTO_DELETE, BOX_CLUSTER_ID, "[%u]", 1) != 0)
 		diag_raise();
 
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_BOOTSTRAP_START;
+	trigger_run(&on_ctl_trigger, &ctl_event);
+
 	/* Register the first replica in the replica set */
 	box_register_replica(replica_id, &INSTANCE_UUID);
 	assert(replica_by_uuid(&INSTANCE_UUID)->id == 1);
@@ -1690,6 +1699,9 @@ bootstrap_master(const struct tt_uuid *replicaset_uuid)
 	if (engine_begin_checkpoint() ||
 	    engine_commit_checkpoint(&replicaset.vclock))
 		panic("failed to create a checkpoint");
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_BOOTSTRAP_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 }
 
 /**
@@ -1703,6 +1715,11 @@ bootstrap_master(const struct tt_uuid *replicaset_uuid)
 static void
 bootstrap_from_master(struct replica *master)
 {
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_INITIAL_JOIN_START;
+	trigger_run(&on_ctl_trigger, &ctl_event);
+
 	struct applier *applier = master->applier;
 	assert(applier != NULL);
 	applier_resume_to_state(applier, APPLIER_READY, TIMEOUT_INFINITY);
@@ -1725,6 +1742,9 @@ bootstrap_from_master(struct replica *master)
 	 */
 	engine_begin_initial_recovery_xc(NULL);
 	applier_resume_to_state(applier, APPLIER_FINAL_JOIN, TIMEOUT_INFINITY);
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_INITIAL_JOIN_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 
 	/*
 	 * Process final data (WALs).
@@ -1735,6 +1755,9 @@ bootstrap_from_master(struct replica *master)
 	journal_set(&journal.base);
 
 	applier_resume_to_state(applier, APPLIER_JOINED, TIMEOUT_INFINITY);
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_FINAL_JOIN_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 
 	/* Clear the pointer to journal before it goes out of scope */
 	journal_set(NULL);
@@ -1883,13 +1906,22 @@ local_recovery(const struct tt_uuid *instance_uuid,
 	 */
 	memtx_engine_recover_snapshot_xc(memtx, checkpoint_vclock);
 
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_SNAPSHOT_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
+
 	engine_begin_final_recovery_xc();
 	recover_remaining_wals(recovery, &wal_stream.base, NULL, false);
 	/*
 	 * Leave hot standby mode, if any, only after
 	 * acquiring the lock.
 	 */
+
 	if (wal_dir_lock < 0) {
+		ctl_event.type = CTL_RECOVERY;
+		ctl_event.recovery.status = CTL_RECOVERY_HOT_STANDBY_START;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 		title("hot_standby");
 		say_info("Entering hot standby mode");
 		recovery_follow_local(recovery, &wal_stream.base, "hot_standby",
@@ -1908,11 +1940,17 @@ local_recovery(const struct tt_uuid *instance_uuid,
 		 * applied in hot standby mode.
 		 */
 		vclock_copy(&replicaset.vclock, &recovery->vclock);
+		ctl_event.type = CTL_RECOVERY;
+		ctl_event.recovery.status = CTL_RECOVERY_HOT_STANDBY_DONE;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 		box_listen();
 		box_sync_replication(false);
 	}
 	recovery_finalize(recovery);
 	engine_end_recovery_xc();
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_XLOGS_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 
 	/* Check replica set UUID. */
 	if (!tt_uuid_is_nil(replicaset_uuid) &&
@@ -2021,6 +2059,10 @@ box_cfg_xc(void)
 	bool is_bootstrap_leader = false;
 	if (last_checkpoint_lsn >= 0) {
 		/* Recover the instance from the local directory */
+		struct on_ctl_event ctl_event;
+		ctl_event.type = CTL_RECOVERY;
+		ctl_event.recovery.status = CTL_RECOVERY_SNAPSHOT_START;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 		local_recovery(&instance_uuid, &replicaset_uuid,
 			       &last_checkpoint_vclock);
 	} else {
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index 694b5bfd3..140803384 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -52,6 +52,7 @@
 #include "box/lua/stat.h"
 #include "box/lua/info.h"
 #include "box/lua/ctl.h"
+#include "box/lua/ctl_event.h"
 #include "box/lua/session.h"
 #include "box/lua/net_box.h"
 #include "box/lua/cfg.h"
@@ -306,6 +307,7 @@ box_lua_init(struct lua_State *L)
 	box_lua_info_init(L);
 	box_lua_stat_init(L);
 	box_lua_ctl_init(L);
+	box_lua_ctl_event_init(L);
 	box_lua_session_init(L);
 	box_lua_xlog_init(L);
 	luaopen_net_box(L);
diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index c68a3583f..1935143a6 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -397,6 +397,7 @@ local box_cfg_guard_whitelist = {
     tuple = true;
     runtime = true;
     NULL = true;
+    ctl_event = true;
 };
 
 local box = require('box')
diff --git a/test/box/ctl_event.result b/test/box/ctl_event.result
new file mode 100644
index 000000000..5da8cf40d
--- /dev/null
+++ b/test/box/ctl_event.result
@@ -0,0 +1,364 @@
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- create master instance
+test_run:cmd("create server trig_master with script='box/lua/trig_master.lua'")
+---
+- true
+...
+test_run:cmd("start server trig_master")
+---
+- true
+...
+test_run:cmd("switch trig_master")
+---
+- true
+...
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- simple ctl_event_case
+ctl_const = box.ctl_event.const()
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+function on_replace(old, new)
+    return box.tuple.new({new[1], new[2], 'M'})
+end;
+---
+...
+function on_ctl_trig(event)
+    if event.type == ctl_const.SPACE and
+       event.action == ctl_const.SPACE_CREATE then
+        local space = box.space[event.space_id]
+        space:before_replace(on_replace)
+    end
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+active_trig = box.ctl_event.on_ctl_event(on_ctl_trig)
+---
+...
+t1 = box.schema.space.create('trig1')
+---
+...
+_ = t1:create_index('pk')
+---
+...
+t1:replace({1, 2})
+---
+- [1, 2, 'M']
+...
+t1:select()
+---
+- - [1, 2, 'M']
+...
+-- clear the trigger
+box.ctl_event.on_ctl_event(nil, active_trig)
+---
+...
+t2 = box.schema.space.create('trig2')
+---
+...
+_ = t2:create_index('pk')
+---
+...
+t2:replace({1, 2})
+---
+- [1, 2]
+...
+t2:select()
+---
+- - [1, 2]
+...
+test_run:cmd("push filter 'replica: [-0-9a-f]+' to 'server: <master-uuid>'")
+---
+- true
+...
+test_run:cmd("push filter 'space_id: [0-9]+' to 'space_id: <space_id>'")
+---
+- true
+...
+-- create replica and test bootstrap events
+box.schema.user.grant('guest', 'replication', nil, nil, {if_not_exists = true})
+---
+...
+test_run:cmd("create server trig_replica with rpl_master=trig_master, script='box/lua/trig_replica.lua'")
+---
+- true
+...
+test_run:cmd("start server trig_replica")
+---
+- true
+...
+t1:replace({2, 3})
+---
+- [2, 3, 'M']
+...
+t1:select()
+---
+- - [1, 2, 'M']
+  - [2, 3, 'M']
+...
+test_run:cmd("switch trig_replica")
+---
+- true
+...
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- list all events
+events
+---
+- - type: 4
+    server: <master-uuid>
+    status: 1
+  - type: 1
+    status: 8
+  - type: 4
+    server: <master-uuid>
+    status: 2
+  - type: 4
+    server: <master-uuid>
+    status: 4
+  - type: 4
+    server: <master-uuid>
+    status: 5
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 1
+    status: 9
+  - type: 4
+    server: <master-uuid>
+    status: 6
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 1
+    status: 10
+  - type: 4
+    server: <master-uuid>
+    status: 7
+  - type: 4
+    server: <master-uuid>
+    status: 4
+  - type: 4
+    server: <master-uuid>
+    status: 8
+  - type: 4
+    server: <master-uuid>
+    status: 9
+...
+-- check before replace trigger
+box.space.trig1:select()
+---
+- - [1, 2, 'M', 'R']
+  - [2, 3, 'M', 'R']
+...
+test_run:cmd("switch trig_master")
+---
+- true
+...
+test_run:cmd("stop server trig_replica")
+---
+- true
+...
+t1:replace({3, 4})
+---
+- [3, 4, 'M']
+...
+test_run:cmd("start server trig_replica")
+---
+- true
+...
+test_run:cmd("switch trig_replica")
+---
+- true
+...
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- list all events
+events
+---
+- - type: 1
+    status: 1
+  - type: 4
+    server: <master-uuid>
+    status: 1
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 1
+    status: 2
+  - type: 1
+    status: 5
+  - type: 4
+    server: <master-uuid>
+    status: 2
+  - type: 4
+    server: <master-uuid>
+    status: 4
+  - type: 4
+    server: <master-uuid>
+    status: 8
+  - type: 4
+    server: <master-uuid>
+    status: 9
+...
+-- check tuples changed only one time
+box.space.trig1:select()
+---
+- - [1, 2, 'M', 'R']
+  - [2, 3, 'M', 'R']
+  - [3, 4, 'M', 'R']
+...
+test_run:cmd("switch default")
+---
+- true
+...
+test_run:cmd("stop server trig_replica")
+---
+- true
+...
+test_run:cmd("stop server trig_master")
+---
+- true
+...
diff --git a/test/box/ctl_event.test.lua b/test/box/ctl_event.test.lua
new file mode 100644
index 000000000..8cb97d80f
--- /dev/null
+++ b/test/box/ctl_event.test.lua
@@ -0,0 +1,76 @@
+env = require('test_run')
+test_run = env.new()
+
+-- create master instance
+test_run:cmd("create server trig_master with script='box/lua/trig_master.lua'")
+test_run:cmd("start server trig_master")
+test_run:cmd("switch trig_master")
+env = require('test_run')
+test_run = env.new()
+
+-- simple ctl_event_case
+ctl_const = box.ctl_event.const()
+test_run:cmd("setopt delimiter ';'")
+function on_replace(old, new)
+    return box.tuple.new({new[1], new[2], 'M'})
+end;
+function on_ctl_trig(event)
+    if event.type == ctl_const.SPACE and
+       event.action == ctl_const.SPACE_CREATE then
+        local space = box.space[event.space_id]
+        space:before_replace(on_replace)
+    end
+end;
+test_run:cmd("setopt delimiter ''");
+active_trig = box.ctl_event.on_ctl_event(on_ctl_trig)
+t1 = box.schema.space.create('trig1')
+_ = t1:create_index('pk')
+t1:replace({1, 2})
+t1:select()
+
+-- clear the trigger
+box.ctl_event.on_ctl_event(nil, active_trig)
+t2 = box.schema.space.create('trig2')
+_ = t2:create_index('pk')
+t2:replace({1, 2})
+t2:select()
+
+test_run:cmd("push filter 'replica: [-0-9a-f]+' to 'server: <master-uuid>'")
+test_run:cmd("push filter 'space_id: [0-9]+' to 'space_id: <space_id>'")
+
+-- create replica and test bootstrap events
+box.schema.user.grant('guest', 'replication', nil, nil, {if_not_exists = true})
+test_run:cmd("create server trig_replica with rpl_master=trig_master, script='box/lua/trig_replica.lua'")
+test_run:cmd("start server trig_replica")
+t1:replace({2, 3})
+t1:select()
+
+test_run:cmd("switch trig_replica")
+env = require('test_run')
+test_run = env.new()
+
+-- list all events
+events
+
+-- check before replace trigger
+box.space.trig1:select()
+
+test_run:cmd("switch trig_master")
+test_run:cmd("stop server trig_replica")
+
+t1:replace({3, 4})
+
+test_run:cmd("start server trig_replica")
+
+test_run:cmd("switch trig_replica")
+env = require('test_run')
+test_run = env.new()
+
+-- list all events
+events
+
+-- check tuples changed only one time
+box.space.trig1:select()
+test_run:cmd("switch default")
+test_run:cmd("stop server trig_replica")
+test_run:cmd("stop server trig_master")
diff --git a/test/box/lua/trig_master.lua b/test/box/lua/trig_master.lua
new file mode 100644
index 000000000..fa253ba4f
--- /dev/null
+++ b/test/box/lua/trig_master.lua
@@ -0,0 +1,8 @@
+#!/usr/bin/env tarantool
+require('console').listen(os.getenv('ADMIN'))
+
+box.cfg({
+    listen              = os.getenv("LISTEN"),
+    memtx_memory        = 107374182,
+    replication_connect_timeout = 0.5,
+})
diff --git a/test/box/lua/trig_replica.lua b/test/box/lua/trig_replica.lua
new file mode 100644
index 000000000..581bdfdb7
--- /dev/null
+++ b/test/box/lua/trig_replica.lua
@@ -0,0 +1,46 @@
+#!/usr/bin/env tarantool
+require('console').listen(os.getenv('ADMIN'))
+
+events = {}
+
+local ctl_const = box.ctl_event.const()
+local recovery_status = nil
+
+local function before_replace(old, new)
+    if recovery_status == ctl_const.RECOVERY_SNAPSHOT_START or
+       recovery_status == ctl_const.RECOVERY_SNAPSHOT_DONE then
+       -- local files
+       return new
+    end
+    if new == nil then
+        return new
+    end
+    local k = {new:unpack()}
+    table.insert(k, 'R')
+    return box.tuple.new(k)
+end
+
+
+local function ctl_event_trigger(event)
+    -- register the event
+    table.insert(events, event)
+    if event.type == ctl_const.RECOVERY then
+        recovery_status = event.status
+    end
+    if event.type == ctl_const.SPACE and
+       event.action == ctl_const.SPACE_CREATE then
+        if event.space_id > 511 then
+            local space = box.space[event.space_id]
+            space:before_replace(before_replace)
+        end
+    end
+end
+
+box.ctl_event.on_ctl_event(ctl_event_trigger)
+
+box.cfg({
+    listen              = os.getenv("LISTEN"),
+    replication         = os.getenv("MASTER"),
+    memtx_memory        = 107374182,
+    replication_connect_timeout = 0.5,
+})
diff --git a/test/box/misc.result b/test/box/misc.result
index e213d7964..969081e59 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -62,6 +62,7 @@ t
   - cfg
   - commit
   - ctl
+  - ctl_event
   - error
   - feedback
   - index
-- 
2.18.0

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [tarantool-patches] [PATCH 1/2] Update lua space cache just after creation
  2018-08-28 16:19 ` [tarantool-patches] [PATCH 1/2] Update lua space cache just after creation Georgy Kirichenko
@ 2018-08-30 12:06   ` Vladimir Davydov
  2018-08-31  4:57     ` [tarantool-patches] " Georgy Kirichenko
  2018-08-30 12:31   ` Konstantin Osipov
  1 sibling, 1 reply; 15+ messages in thread
From: Vladimir Davydov @ 2018-08-30 12:06 UTC (permalink / raw)
  To: Georgy Kirichenko; +Cc: tarantool-patches

On Tue, Aug 28, 2018 at 07:19:12PM +0300, Georgy Kirichenko wrote:
> The lua space cache (box.space.*) should be valid just after space is
> created because space is ready to accept new records or does not exists
> before wal would be written. So invoke a space create/drop trigger after
> a space is changed and recall it in a case of rollback.
> 
> Relates: 3159

Nit: should be

Needed for #3159

> diff --git a/src/box/alter.cc b/src/box/alter.cc
> index a6299a12e..b2758a4d9 100644
> --- a/src/box/alter.cc
> +++ b/src/box/alter.cc
> @@ -1626,12 +1616,10 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
>  		 * so it's safe to simply drop the space on
>  		 * rollback.
>  		 */
> -		struct trigger *on_commit =
> -			txn_alter_trigger_new(on_create_space_commit, space);
> -		txn_on_commit(txn, on_commit);
>  		struct trigger *on_rollback =
>  			txn_alter_trigger_new(on_create_space_rollback, space);
>  		txn_on_rollback(txn, on_rollback);
> +		trigger_run_xc(&on_alter_space, space);
>  	} else if (new_tuple == NULL) { /* DELETE */
>  		access_check_ddl(old_space->def->name, old_space->def->id,
>  				 old_space->def->uid, SC_SPACE, PRIV_D, true);
> @@ -1674,6 +1662,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
>  		struct trigger *on_rollback =
>  			txn_alter_trigger_new(on_drop_space_rollback, space);
>  		txn_on_rollback(txn, on_rollback);
> +		trigger_run_xc(&on_alter_space, old_space);
>  	} else { /* UPDATE, REPLACE */
>  		assert(old_space != NULL && new_tuple != NULL);
>  		struct space_def *def =
> @@ -1730,6 +1719,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
>  		(void) new UpdateSchemaVersion(alter);
>  		alter_space_do(txn, alter);
>  		alter_guard.is_active = false;
> +		trigger_run_xc(&on_alter_space, alter->new_space);
>  	}
>  }
>  
> @@ -1931,6 +1921,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
>  	(void) new UpdateSchemaVersion(alter);
>  	alter_space_do(txn, alter);
>  	scoped_guard.is_active = false;
> +	trigger_run_xc(&on_alter_space, alter->new_space);
>  }

Why not call this trigger from alter_space_do?

> diff --git a/test/box/errinj.test.lua b/test/box/errinj.test.lua
> index a3ea659aa..32569f605 100644
> --- a/test/box/errinj.test.lua
> +++ b/test/box/errinj.test.lua
> @@ -557,3 +557,30 @@ fio = require('fio')
>  #fio.glob(fio.pathjoin(box.cfg.vinyl_dir, box.space.test.id, 0, '*.index.inprogress')) == 0
>  
>  box.space.test:drop()
> +
> +
> +-- allocate a space id to prevent max space id update

Why?

> +trig = box.schema.space.create('trig')
> +trig_id = trig.id
> +trig:drop()
> +trig = nil
> +fiber = require('fiber')
> +ch = fiber.channel(1)
> +errinj = box.error.injection
> +test_run:cmd("setopt delimiter ';'")
> +-- check space exists just after creation
> +errinj.set("ERRINJ_WAL_WRITE", true);
> +_ = fiber.create(function ()
> +        fiber.create(function ()
> +            pcall(box.schema.space.create, 'trig', {id = trig_id})
> +            ch:put(true)
> +        end)
> +        trig = box.space.trig
> +    end);
> +trig ~= nil;
> +ch:get();
> +--and not exists after rollback
> +box.space.trig;
> +test_run:cmd("setopt delimiter ''");
> +
> +errinj.set("ERRINJ_WAL_WRITE", false)

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [tarantool-patches] [PATCH 2/2] On ctl event trigger
  2018-08-28 16:19 ` [tarantool-patches] [PATCH 2/2] On ctl event trigger Georgy Kirichenko
@ 2018-08-30 12:07   ` Vladimir Davydov
  2018-08-30 12:10   ` Vladimir Davydov
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 15+ messages in thread
From: Vladimir Davydov @ 2018-08-30 12:07 UTC (permalink / raw)
  To: Georgy Kirichenko; +Cc: tarantool-patches

On Tue, Aug 28, 2018 at 07:19:13PM +0300, Georgy Kirichenko wrote:
> ---
>  src/box/CMakeLists.txt        |   2 +
>  src/box/alter.cc              |  16 ++
>  src/box/applier.cc            |   6 +
>  src/box/box.cc                |  42 ++++
>  src/box/lua/init.c            |   2 +
>  src/box/lua/load_cfg.lua      |   1 +
>  test/box/ctl_event.result     | 364 ++++++++++++++++++++++++++++++++++
>  test/box/ctl_event.test.lua   |  76 +++++++
>  test/box/lua/trig_master.lua  |   8 +
>  test/box/lua/trig_replica.lua |  46 +++++
>  test/box/misc.result          |   1 +
>  11 files changed, 564 insertions(+)
>  create mode 100644 test/box/ctl_event.result
>  create mode 100644 test/box/ctl_event.test.lua
>  create mode 100644 test/box/lua/trig_master.lua
>  create mode 100644 test/box/lua/trig_replica.lua
> 
> diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
> index cab6a2276..ee495a037 100644
> --- a/src/box/CMakeLists.txt
> +++ b/src/box/CMakeLists.txt
> @@ -113,6 +113,7 @@ add_library(box STATIC
>      journal.c
>      wal.c
>      call.c
> +    ctl_event.c

There's no ctl_event.c so this doesn't compile. Please fix.

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [tarantool-patches] [PATCH 2/2] On ctl event trigger
  2018-08-28 16:19 ` [tarantool-patches] [PATCH 2/2] On ctl event trigger Georgy Kirichenko
  2018-08-30 12:07   ` Vladimir Davydov
@ 2018-08-30 12:10   ` Vladimir Davydov
  2018-08-30 12:38   ` Vladimir Davydov
  2018-08-30 12:50   ` Konstantin Osipov
  3 siblings, 0 replies; 15+ messages in thread
From: Vladimir Davydov @ 2018-08-30 12:10 UTC (permalink / raw)
  To: Georgy Kirichenko; +Cc: tarantool-patches

On Tue, Aug 28, 2018 at 07:19:13PM +0300, Georgy Kirichenko wrote:
> Introduce a ctl event trigger fired in cases of a bootstrap/recovery status
> changes, a space create/alter/drop action, an applier state change and
> shutdown. Trigger could be set with box.ctl_event even before the first
> box.cfg invocation to control recovery and bootstrap behavior.
> 
> Event constants accessible via box.ctl_event.const()
> There are events:
>   - RECOVERY
>   - SPACE
>   - SHUTDOWN
>   - APPLIER
> 
> A recovery event might have a status:
>    * RECOVERY_SNAPSHOT_START
>    * RECOVERY_SNAPSHOT_DONE
>    * RECOVERY_HOT_STANDBY_START
>    * RECOVERY_HOT_STANDBY_DONE
>    * RECOVERY_XLOGS_DONE
>    * RECOVERY_BOOTSTRAP_START
>    * RECOVERY_BOOTSTRAP_DONE
>    * RECOVERY_INITIAL_JOIN_START
>    * RECOVERY_INITIAL_JOIN_DONE
>    * RECOVERY_FINAL_JOIN_DONE
> 
> A space event consists of space identifier and action:
>    * SPACE_CREATE
>    * SPACE_ALTER
>    * SPACE_DELETE
> 
> An applier event contains peer uuid and state:
>    * APPLIER_OFF
>    * APPLIER_CONNECT
>    * APPLIER_CONNECTED
>    * APPLIER_AUTH
>    * APPLIER_READY
>    * APPLIER_INITIAL_JOIN
>    * APPLIER_FINAL_JOIN
>    * APPLIER_JOINED
>    * APPLIER_SYNC
>    * APPLIER_FOLLOW
>    * APPLIER_STOPPED
>    * APPLIER_DISCONNECTED
>    * APPLIER_LOADING
> 
> Fixes: #3159

Nit: should be

Closes #3159


From the commit message it's unclear how to use this trigger.
Please describe.

Also, please append a documentation request to the commit message.

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [tarantool-patches] Re: [PATCH 1/2] Update lua space cache just after creation
  2018-08-28 16:19 ` [tarantool-patches] [PATCH 1/2] Update lua space cache just after creation Georgy Kirichenko
  2018-08-30 12:06   ` Vladimir Davydov
@ 2018-08-30 12:31   ` Konstantin Osipov
  2018-08-31  4:53     ` Georgy Kirichenko
  1 sibling, 1 reply; 15+ messages in thread
From: Konstantin Osipov @ 2018-08-30 12:31 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Georgy Kirichenko

* Georgy Kirichenko <georgy@tarantool.org> [18/08/28 19:20]:
> The lua space cache (box.space.*) should be valid just after space is
> created because space is ready to accept new records or does not exists
> before wal would be written. So invoke a space create/drop trigger after
> a space is changed and recall it in a case of rollback.

I understand what this patch is doing, but I don't understand why
you need it.

We need to alert the user about system events, such as newly
created spaces, including system spaces.

We can well use txn->on_commit trigger for this purpose, having
set an on_commit trigger which pushes on_ctl events. This trigger
is invoked *after* a record is written to wal in txn.cc. Why do
you need to manipulate with the internal alter.cc triggers at all?
Why is the order of invocation of these triggers important? 

Ideally, your on_ctl trigger should be invoked after all the alter
triggers ahve been invoked. For that, all you need to do is to
ensure that on_ctl trigger is added *after* the alter trigger -
i.e. it has a lower priority/order than alter trigger. Is your
problem with trigger ordering? Is it that you've been trying to
add alter trigger *after* you added the on_ctl trigger, and, since
triggers are normally added to the end of the list, when on_ctl
trigger was invoked the space was not yet available in box.space?

If it is the case, then I think we should simply add
trigger_add_first() function, and use this function to add alter
triggers to the beginning of the list.

That would solve the problem until we have more strict ordering
requirements, at which point we could add trigger->order member
and trigger_add_with_order() method.

Finally, I don't see how the test case is testing the order. I
believe the test case should pass both before and after the patch.

> @@ -1571,3 +1571,64 @@ fio = require('fio')
>  box.space.test:drop()
>  ---
>  ...
> +-- allocate a space id to prevent max space id update

Why is test description missing in the result file?


Nitpick: please add leading -- and trailing -- to the test
description, start it with a capital letter and end with a dot.


Thanks for the patch,


-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [tarantool-patches] [PATCH 2/2] On ctl event trigger
  2018-08-28 16:19 ` [tarantool-patches] [PATCH 2/2] On ctl event trigger Georgy Kirichenko
  2018-08-30 12:07   ` Vladimir Davydov
  2018-08-30 12:10   ` Vladimir Davydov
@ 2018-08-30 12:38   ` Vladimir Davydov
  2018-08-30 13:04     ` Georgy Kirichenko
                       ` (2 more replies)
  2018-08-30 12:50   ` Konstantin Osipov
  3 siblings, 3 replies; 15+ messages in thread
From: Vladimir Davydov @ 2018-08-30 12:38 UTC (permalink / raw)
  To: Georgy Kirichenko; +Cc: tarantool-patches

On Tue, Aug 28, 2018 at 07:19:13PM +0300, Georgy Kirichenko wrote:
> Introduce a ctl event trigger fired in cases of a bootstrap/recovery status
> changes, a space create/alter/drop action, an applier state change and
> shutdown. Trigger could be set with box.ctl_event even before the first
> box.cfg invocation to control recovery and bootstrap behavior.
> 
> Event constants accessible via box.ctl_event.const()
> There are events:
>   - RECOVERY
>   - SPACE
>   - SHUTDOWN
>   - APPLIER
> 
> A recovery event might have a status:
>    * RECOVERY_SNAPSHOT_START
>    * RECOVERY_SNAPSHOT_DONE
>    * RECOVERY_HOT_STANDBY_START
>    * RECOVERY_HOT_STANDBY_DONE
>    * RECOVERY_XLOGS_DONE
>    * RECOVERY_BOOTSTRAP_START
>    * RECOVERY_BOOTSTRAP_DONE
>    * RECOVERY_INITIAL_JOIN_START
>    * RECOVERY_INITIAL_JOIN_DONE
>    * RECOVERY_FINAL_JOIN_DONE
> 
> A space event consists of space identifier and action:
>    * SPACE_CREATE
>    * SPACE_ALTER
>    * SPACE_DELETE
> 
> An applier event contains peer uuid and state:
>    * APPLIER_OFF
>    * APPLIER_CONNECT
>    * APPLIER_CONNECTED
>    * APPLIER_AUTH
>    * APPLIER_READY
>    * APPLIER_INITIAL_JOIN
>    * APPLIER_FINAL_JOIN
>    * APPLIER_JOINED
>    * APPLIER_SYNC
>    * APPLIER_FOLLOW
>    * APPLIER_STOPPED
>    * APPLIER_DISCONNECTED
>    * APPLIER_LOADING

I can't review this patch properly, because ctl_event.[hc] are missing,
but I've a few notes about the API.

First, I don't understand why you need two kinds of constants. I mean
SPACE and SPACE_CREATE, or RECOVERY and RECOVERY_SNAPSHOT_START. IMO
there should be just one set of constants.

Second, IMO the event trigger should be installed with box.ctl.on_event
(not box.ctl_event.on_ctl_event), and constants should be defined in
box.ctl.event (not box.ctl_event.const()).

Third, let's start with fewer events, only ones that we need to right
now (e.g. for replication conflict resolution with before_replace).
We can add other events later, when we need to.

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [tarantool-patches] Re: [PATCH 2/2] On ctl event trigger
  2018-08-28 16:19 ` [tarantool-patches] [PATCH 2/2] On ctl event trigger Georgy Kirichenko
                     ` (2 preceding siblings ...)
  2018-08-30 12:38   ` Vladimir Davydov
@ 2018-08-30 12:50   ` Konstantin Osipov
  3 siblings, 0 replies; 15+ messages in thread
From: Konstantin Osipov @ 2018-08-30 12:50 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Georgy Kirichenko

* Georgy Kirichenko <georgy@tarantool.org> [18/08/28 19:20]:
> +		struct on_ctl_event event;
> +		event.type = CTL_SPACE;
> +		event.space.action = CTL_SPACE_CREATE;
> +		event.space.space_id = old_id;
> +		trigger_run(&on_ctl_trigger, &event);

event.type and event.action confuses me. Why do we need both? I
assume because you associate a context to a type, and events of
the same "event type" have the same context. But it seems the
context is defined fully by trigger invocation context, no? 

Please keep in mind that we have been solving the same problem
with audit events. Looks like we need to pull and open source some
of the infrastructure code, such as struct event declaration, so
that we can reuse it for all kinds of events, huh...

>  	if (is_box_configured) {
> +		struct on_ctl_event ctl_event;
> +		ctl_event.type = CTL_SHUTDOWN;
> +		trigger_run(&on_ctl_trigger, &ctl_event);

I don't understand why we call it 'ctl event'. Why not call it
'shutdown_trigger_ctx'? 

Simply put, we begin invoking more triggers, at more places. The
context of the trigger is defined, well, but the context of the
place it is invoked from, not by event type.

ctl_event.h/c seem to be missing?

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [tarantool-patches] [PATCH 2/2] On ctl event trigger
  2018-08-30 12:38   ` Vladimir Davydov
@ 2018-08-30 13:04     ` Georgy Kirichenko
  2018-08-30 13:21     ` Vladimir Davydov
  2018-08-30 14:40     ` Konstantin Osipov
  2 siblings, 0 replies; 15+ messages in thread
From: Georgy Kirichenko @ 2018-08-30 13:04 UTC (permalink / raw)
  To: Vladimir Davydov; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 34216 bytes --]

Sorry, i forgot to attach some files in the last commit, there is updated diff 
version. Corresponding branch is updated.

commit f77206197559df7acc0cf53822c2aecadbf5a04e
Author: Georgy Kirichenko <georgy@tarantool.org>
Date:   Tue Aug 28 18:09:24 2018 +0300

    On ctl event trigger
    
    Introduce a ctl event trigger fired in cases of a bootstrap/recovery status
    changes, a space create/alter/drop action, an applier state change and
    shutdown. Trigger could be set with box.ctl_event even before the first
    box.cfg invocation to control recovery and bootstrap behavior.
    
    Event constants accessible via box.ctl_event.const()
    There are events:
      - RECOVERY
      - SPACE
      - SHUTDOWN
      - APPLIER
    
    A recovery event might have a status:
       * RECOVERY_SNAPSHOT_START
       * RECOVERY_SNAPSHOT_DONE
       * RECOVERY_HOT_STANDBY_START
       * RECOVERY_HOT_STANDBY_DONE
       * RECOVERY_XLOGS_DONE
       * RECOVERY_BOOTSTRAP_START
       * RECOVERY_BOOTSTRAP_DONE
       * RECOVERY_INITIAL_JOIN_START
       * RECOVERY_INITIAL_JOIN_DONE
       * RECOVERY_FINAL_JOIN_DONE
    
    A space event consists of space identifier and action:
       * SPACE_CREATE
       * SPACE_ALTER
       * SPACE_DELETE
    
    An applier event contains peer uuid and state:
       * APPLIER_OFF
       * APPLIER_CONNECT
       * APPLIER_CONNECTED
       * APPLIER_AUTH
       * APPLIER_READY
       * APPLIER_INITIAL_JOIN
       * APPLIER_FINAL_JOIN
       * APPLIER_JOINED
       * APPLIER_SYNC
       * APPLIER_FOLLOW
       * APPLIER_STOPPED
       * APPLIER_DISCONNECTED
       * APPLIER_LOADING
    
    Fixes: #3159

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index cab6a2276..ee495a037 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -113,6 +113,7 @@ add_library(box STATIC
     journal.c
     wal.c
     call.c
+    ctl_event.c
     ${lua_sources}
     lua/init.c
     lua/call.c
@@ -131,6 +132,7 @@ add_library(box STATIC
     lua/session.c
     lua/net_box.c
     lua/xlog.c
+    lua/ctl_event.c
     ${bin_sources})
 
 target_link_libraries(box box_error tuple stat xrow xlog vclock crc32 
scramble
diff --git a/src/box/alter.cc b/src/box/alter.cc
index b2758a4d9..8e44ada9b 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -52,6 +52,7 @@
 #include "identifier.h"
 #include "version.h"
 #include "sequence.h"
+#include "ctl_event.h"
 
 /**
  * chap-sha1 of empty string, i.e.
@@ -1620,6 +1621,11 @@ on_replace_dd_space(struct trigger * /* trigger */, 
void *event)
 			txn_alter_trigger_new(on_create_space_rollback, space);
 		txn_on_rollback(txn, on_rollback);
 		trigger_run_xc(&on_alter_space, space);
+		struct on_ctl_event event;
+		event.type = CTL_SPACE;
+		event.space.action = CTL_SPACE_CREATE;
+		event.space.space_id = old_id;
+		trigger_run(&on_ctl_trigger, &event);
 	} else if (new_tuple == NULL) { /* DELETE */
 		access_check_ddl(old_space->def->name, old_space->def->id,
 				 old_space->def->uid, SC_SPACE, PRIV_D, true);
@@ -1663,6 +1669,11 @@ on_replace_dd_space(struct trigger * /* trigger */, 
void *event)
 			txn_alter_trigger_new(on_drop_space_rollback, space);
 		txn_on_rollback(txn, on_rollback);
 		trigger_run_xc(&on_alter_space, old_space);
+		struct on_ctl_event event;
+		event.type = CTL_SPACE;
+		event.space.action = CTL_SPACE_DELETE;
+		event.space.space_id = old_id;
+		trigger_run(&on_ctl_trigger, &event);
 	} else { /* UPDATE, REPLACE */
 		assert(old_space != NULL && new_tuple != NULL);
 		struct space_def *def =
@@ -1720,6 +1731,11 @@ on_replace_dd_space(struct trigger * /* trigger */, 
void *event)
 		alter_space_do(txn, alter);
 		alter_guard.is_active = false;
 		trigger_run_xc(&on_alter_space, alter->new_space);
+		struct on_ctl_event event;
+		event.type = CTL_SPACE;
+		event.space.action = CTL_SPACE_ALTER;
+		event.space.space_id = old_id;
+		trigger_run(&on_ctl_trigger, &event);
 	}
 }
 
diff --git a/src/box/applier.cc b/src/box/applier.cc
index 28df8f7ca..4a42c6b43 100644
--- a/src/box/applier.cc
+++ b/src/box/applier.cc
@@ -48,6 +48,7 @@
 #include "error.h"
 #include "session.h"
 #include "cfg.h"
+#include "ctl_event.h"
 
 STRS(applier_state, applier_STATE);
 
@@ -58,6 +59,11 @@ applier_set_state(struct applier *applier, enum 
applier_state state)
 	say_debug("=> %s", applier_state_strs[state] +
 		  strlen("APPLIER_"));
 	trigger_run_xc(&applier->on_state, applier);
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_APPLIER;
+	ctl_event.applier.replica_uuid = applier->uuid;
+	ctl_event.applier.status = state;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 }
 
 /**
diff --git a/src/box/box.cc b/src/box/box.cc
index 0004140e9..01e512cc2 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -73,6 +73,7 @@
 #include "call.h"
 #include "func.h"
 #include "sequence.h"
+#include "ctl_event.h"
 
 static char status[64] = "unknown";
 
@@ -1602,6 +1603,9 @@ box_free(void)
 	 * initialized
 	 */
 	if (is_box_configured) {
+		struct on_ctl_event ctl_event;
+		ctl_event.type = CTL_SHUTDOWN;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 #if 0
 		session_free();
 		user_cache_free();
@@ -1670,6 +1674,11 @@ bootstrap_master(const struct tt_uuid *replicaset_uuid)
 	if (boxk(IPROTO_DELETE, BOX_CLUSTER_ID, "[%u]", 1) != 0)
 		diag_raise();
 
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_BOOTSTRAP_START;
+	trigger_run(&on_ctl_trigger, &ctl_event);
+
 	/* Register the first replica in the replica set */
 	box_register_replica(replica_id, &INSTANCE_UUID);
 	assert(replica_by_uuid(&INSTANCE_UUID)->id == 1);
@@ -1690,6 +1699,9 @@ bootstrap_master(const struct tt_uuid *replicaset_uuid)
 	if (engine_begin_checkpoint() ||
 	    engine_commit_checkpoint(&replicaset.vclock))
 		panic("failed to create a checkpoint");
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_BOOTSTRAP_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 }
 
 /**
@@ -1703,6 +1715,11 @@ bootstrap_master(const struct tt_uuid *replicaset_uuid)
 static void
 bootstrap_from_master(struct replica *master)
 {
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_INITIAL_JOIN_START;
+	trigger_run(&on_ctl_trigger, &ctl_event);
+
 	struct applier *applier = master->applier;
 	assert(applier != NULL);
 	applier_resume_to_state(applier, APPLIER_READY, TIMEOUT_INFINITY);
@@ -1725,6 +1742,9 @@ bootstrap_from_master(struct replica *master)
 	 */
 	engine_begin_initial_recovery_xc(NULL);
 	applier_resume_to_state(applier, APPLIER_FINAL_JOIN, TIMEOUT_INFINITY);
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_INITIAL_JOIN_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 
 	/*
 	 * Process final data (WALs).
@@ -1735,6 +1755,9 @@ bootstrap_from_master(struct replica *master)
 	journal_set(&journal.base);
 
 	applier_resume_to_state(applier, APPLIER_JOINED, TIMEOUT_INFINITY);
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_FINAL_JOIN_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 
 	/* Clear the pointer to journal before it goes out of scope */
 	journal_set(NULL);
@@ -1883,13 +1906,22 @@ local_recovery(const struct tt_uuid *instance_uuid,
 	 */
 	memtx_engine_recover_snapshot_xc(memtx, checkpoint_vclock);
 
+	struct on_ctl_event ctl_event;
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_SNAPSHOT_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
+
 	engine_begin_final_recovery_xc();
 	recover_remaining_wals(recovery, &wal_stream.base, NULL, false);
 	/*
 	 * Leave hot standby mode, if any, only after
 	 * acquiring the lock.
 	 */
+
 	if (wal_dir_lock < 0) {
+		ctl_event.type = CTL_RECOVERY;
+		ctl_event.recovery.status = CTL_RECOVERY_HOT_STANDBY_START;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 		title("hot_standby");
 		say_info("Entering hot standby mode");
 		recovery_follow_local(recovery, &wal_stream.base, "hot_standby",
@@ -1908,11 +1940,17 @@ local_recovery(const struct tt_uuid *instance_uuid,
 		 * applied in hot standby mode.
 		 */
 		vclock_copy(&replicaset.vclock, &recovery->vclock);
+		ctl_event.type = CTL_RECOVERY;
+		ctl_event.recovery.status = CTL_RECOVERY_HOT_STANDBY_DONE;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 		box_listen();
 		box_sync_replication(false);
 	}
 	recovery_finalize(recovery);
 	engine_end_recovery_xc();
+	ctl_event.type = CTL_RECOVERY;
+	ctl_event.recovery.status = CTL_RECOVERY_XLOGS_DONE;
+	trigger_run(&on_ctl_trigger, &ctl_event);
 
 	/* Check replica set UUID. */
 	if (!tt_uuid_is_nil(replicaset_uuid) &&
@@ -2021,6 +2059,10 @@ box_cfg_xc(void)
 	bool is_bootstrap_leader = false;
 	if (last_checkpoint_lsn >= 0) {
 		/* Recover the instance from the local directory */
+		struct on_ctl_event ctl_event;
+		ctl_event.type = CTL_RECOVERY;
+		ctl_event.recovery.status = CTL_RECOVERY_SNAPSHOT_START;
+		trigger_run(&on_ctl_trigger, &ctl_event);
 		local_recovery(&instance_uuid, &replicaset_uuid,
 			       &last_checkpoint_vclock);
 	} else {
diff --git a/src/box/ctl_event.c b/src/box/ctl_event.c
new file mode 100644
index 000000000..387bdd367
--- /dev/null
+++ b/src/box/ctl_event.c
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2018, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "ctl_event.h"
+
+struct rlist on_ctl_trigger = RLIST_HEAD_INITIALIZER(on_ctl_trigger);
diff --git a/src/box/ctl_event.h b/src/box/ctl_event.h
new file mode 100644
index 000000000..ac57e51b5
--- /dev/null
+++ b/src/box/ctl_event.h
@@ -0,0 +1,100 @@
+#ifndef BOX_CTL_EVENT_H
+#define BOX_CTL_EVENT_H
+
+/*
+ * Copyright 2010-2018, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdint.h>
+
+#include "small/rlist.h"
+#include "tt_uuid.h"
+
+/* Ctl event types. */
+#define CTL_RECOVERY     1
+#define CTL_SPACE        2
+#define CTL_SHUTDOWN     3
+#define CTL_APPLIER      4
+
+/* CTL_RECOVERY event status. */
+#define CTL_RECOVERY_SNAPSHOT_START       1
+#define CTL_RECOVERY_SNAPSHOT_DONE        2
+#define CTL_RECOVERY_HOT_STANDBY_START    3
+#define CTL_RECOVERY_HOT_STANDBY_DONE     4
+#define CTL_RECOVERY_XLOGS_DONE           5
+#define CTL_RECOVERY_BOOTSTRAP_START      6
+#define CTL_RECOVERY_BOOTSTRAP_DONE       7
+#define CTL_RECOVERY_INITIAL_JOIN_START   8
+#define CTL_RECOVERY_INITIAL_JOIN_DONE    9
+#define CTL_RECOVERY_FINAL_JOIN_DONE      10
+
+#define CTL_SPACE_CREATE    1
+#define CTL_SPACE_ALTER     2
+#define CTL_SPACE_DELETE    3
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+extern struct rlist on_ctl_trigger;
+
+
+/* CTL_RECOVERY event specific data. */
+struct on_ctl_recovery_event {
+	uint32_t status;
+};
+
+/* CTL_SPACE event specific data. */
+struct on_ctl_space_event {
+	uint32_t action;
+	uint32_t space_id;
+};
+
+struct on_ctl_applier_event {
+	struct tt_uuid replica_uuid;
+	uint32_t status;
+};
+
+struct on_ctl_event {
+	uint32_t type;
+	union {
+		struct on_ctl_recovery_event recovery;
+		struct on_ctl_space_event space;
+		struct on_ctl_applier_event applier;
+	};
+};
+
+
+#if defined(__cplusplus)
+}
+#endif /* defined(__cplusplus) */
+
+
+#endif
diff --git a/src/box/lua/ctl_event.c b/src/box/lua/ctl_event.c
new file mode 100644
index 000000000..aead1cf23
--- /dev/null
+++ b/src/box/lua/ctl_event.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2010-2018, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+
+#include "trigger.h"
+#include "box/ctl_event.h"
+#include "box/applier.h"
+
+#include "lua/trigger.h"
+#include "lua/utils.h"
+
+static int
+lbox_ctl_event_const(struct lua_State *L)
+{
+	lua_newtable(L);
+	lua_pushstring(L, "RECOVERY");
+	lua_pushinteger(L, CTL_RECOVERY);
+	lua_settable(L, -3);
+	lua_pushstring(L, "SPACE");
+	lua_pushinteger(L, CTL_SPACE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "SHUTDOWN");
+	lua_pushinteger(L, CTL_SHUTDOWN);
+	lua_settable(L, -3);
+
+	lua_pushstring(L, "RECOVERY_SNAPSHOT_START");
+	lua_pushinteger(L, CTL_RECOVERY_SNAPSHOT_START);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_SNAPSHOT_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_SNAPSHOT_DONE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_HOT_STANDBY_START");
+	lua_pushinteger(L, CTL_RECOVERY_HOT_STANDBY_START);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_HOT_STANDBY_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_HOT_STANDBY_DONE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_XLOGS_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_XLOGS_DONE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_BOOTSTRAP_START");
+	lua_pushinteger(L, CTL_RECOVERY_BOOTSTRAP_START);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_BOOTSTRAP_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_BOOTSTRAP_DONE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_INITIAL_JOIN_START");
+	lua_pushinteger(L, CTL_RECOVERY_INITIAL_JOIN_START);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_INITIAL_JOIN_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_INITIAL_JOIN_DONE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "RECOVERY_FINAL_JOIN_DONE");
+	lua_pushinteger(L, CTL_RECOVERY_FINAL_JOIN_DONE);
+	lua_settable(L, -3);
+
+	lua_pushstring(L, "SPACE_CREATE");
+	lua_pushinteger(L, CTL_SPACE_CREATE);
+	lua_settable(L, -3);
+	lua_pushstring(L, "SPACE_ALTER");
+	lua_pushinteger(L, CTL_SPACE_ALTER);
+	lua_settable(L, -3);
+	lua_pushstring(L, "SPACE_DELETE");
+	lua_pushinteger(L, CTL_SPACE_DELETE);
+	lua_settable(L, -3);
+
+	lua_pushstring(L, "APPLIER_OFF");
+	lua_pushinteger(L, APPLIER_OFF);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_CONNECT");
+	lua_pushinteger(L, APPLIER_CONNECT);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_CONNECTED");
+	lua_pushinteger(L, APPLIER_CONNECTED);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_AUTH");
+	lua_pushinteger(L, APPLIER_AUTH);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_READY");
+	lua_pushinteger(L, APPLIER_READY);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_INITIAL_JOIN");
+	lua_pushinteger(L, APPLIER_INITIAL_JOIN);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_FINAL_JOIN");
+	lua_pushinteger(L, APPLIER_FINAL_JOIN);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_JOINED");
+	lua_pushinteger(L, APPLIER_JOINED);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_SYNC");
+	lua_pushinteger(L, APPLIER_SYNC);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_FOLLOW");
+	lua_pushinteger(L, APPLIER_FOLLOW);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_STOPPED");
+	lua_pushinteger(L, APPLIER_STOPPED);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_DISCONNECTED");
+	lua_pushinteger(L, APPLIER_DISCONNECTED);
+	lua_settable(L, -3);
+	lua_pushstring(L, "APPLIER_LOADING");
+	lua_pushinteger(L, APPLIER_LOADING);
+	lua_settable(L, -3);
+
+	return 1;
+}
+
+static int
+lbox_push_on_ctl_event(struct lua_State *L, void *data)
+{
+	struct on_ctl_event *event = (struct on_ctl_event *)data;
+	switch (event->type) {
+	case CTL_RECOVERY:
+		lua_newtable(L);
+		lua_pushstring(L, "type");
+		lua_pushinteger(L, CTL_RECOVERY);
+		lua_settable(L, -3);
+		lua_pushstring(L, "status");
+		lua_pushinteger(L, event->recovery.status);
+		lua_settable(L, -3);
+		break;
+	case CTL_SPACE:
+		lua_newtable(L);
+		lua_pushstring(L, "type");
+		lua_pushinteger(L, CTL_SPACE);
+		lua_settable(L, -3);
+		lua_pushstring(L, "action");
+		lua_pushinteger(L, event->space.action);
+		lua_settable(L, -3);
+		lua_pushstring(L, "space_id");
+		lua_pushinteger(L, event->space.space_id);
+		lua_settable(L, -3);
+		break;
+	case CTL_SHUTDOWN:
+		lua_newtable(L);
+		lua_pushstring(L, "type");
+		lua_pushinteger(L, CTL_SHUTDOWN);
+		lua_settable(L, -3);
+		break;
+	case CTL_APPLIER:
+		lua_newtable(L);
+		lua_pushstring(L, "type");
+		lua_pushinteger(L, CTL_APPLIER);
+		lua_settable(L, -3);
+		lua_pushstring(L, "replica");
+		lua_pushstring(L, tt_uuid_str(&event->applier.replica_uuid));
+		lua_settable(L, -3);
+		lua_pushstring(L, "status");
+		lua_pushinteger(L, event->applier.status);
+		lua_settable(L, -3);
+		break;
+	default:
+		lua_pushnil(L);
+	}
+	return 1;
+}
+
+static int
+lbox_on_ctl_event(struct lua_State *L)
+{
+	return lbox_trigger_reset(L, 2, &on_ctl_trigger,
+				  lbox_push_on_ctl_event, NULL);
+}
+
+static const struct luaL_Reg lbox_ctl_lib[] = {
+	{"const", lbox_ctl_event_const},
+	{"on_ctl_event", lbox_on_ctl_event},
+	{NULL, NULL}
+};
+
+void
+box_lua_ctl_event_init(struct lua_State *L)
+{
+	luaL_register_module(L, "box.ctl_event", lbox_ctl_lib);
+	lua_pop(L, 1);
+}
diff --git a/src/box/lua/ctl_event.h b/src/box/lua/ctl_event.h
new file mode 100644
index 000000000..a878707a3
--- /dev/null
+++ b/src/box/lua/ctl_event.h
@@ -0,0 +1,48 @@
+#ifndef INCLUDES_TARANTOOL_LUA_CTL_EVENT_H
+#define INCLUDES_TARANTOOL_LUA_CTL_EVENT_H
+
+/*
+ * Copyright 2010-2018, Tarantool AUTHORS, please see AUTHORS file.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above
+ *    copyright notice, this list of conditions and the
+ *    following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above
+ *    copyright notice, this list of conditions and the following
+ *    disclaimer in the documentation and/or other materials
+ *    provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+struct lua_State;
+
+void
+box_lua_ctl_event_init(struct lua_State *L);
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
+#endif /* INCLUDES_TARANTOOL_LUA_CTL_EVENT_H */
diff --git a/src/box/lua/init.c b/src/box/lua/init.c
index 694b5bfd3..140803384 100644
--- a/src/box/lua/init.c
+++ b/src/box/lua/init.c
@@ -52,6 +52,7 @@
 #include "box/lua/stat.h"
 #include "box/lua/info.h"
 #include "box/lua/ctl.h"
+#include "box/lua/ctl_event.h"
 #include "box/lua/session.h"
 #include "box/lua/net_box.h"
 #include "box/lua/cfg.h"
@@ -306,6 +307,7 @@ box_lua_init(struct lua_State *L)
 	box_lua_info_init(L);
 	box_lua_stat_init(L);
 	box_lua_ctl_init(L);
+	box_lua_ctl_event_init(L);
 	box_lua_session_init(L);
 	box_lua_xlog_init(L);
 	luaopen_net_box(L);
diff --git a/src/box/lua/load_cfg.lua b/src/box/lua/load_cfg.lua
index c68a3583f..1935143a6 100644
--- a/src/box/lua/load_cfg.lua
+++ b/src/box/lua/load_cfg.lua
@@ -397,6 +397,7 @@ local box_cfg_guard_whitelist = {
     tuple = true;
     runtime = true;
     NULL = true;
+    ctl_event = true;
 };
 
 local box = require('box')
diff --git a/test/box/ctl_event.result b/test/box/ctl_event.result
new file mode 100644
index 000000000..5da8cf40d
--- /dev/null
+++ b/test/box/ctl_event.result
@@ -0,0 +1,364 @@
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- create master instance
+test_run:cmd("create server trig_master with script='box/lua/
trig_master.lua'")
+---
+- true
+...
+test_run:cmd("start server trig_master")
+---
+- true
+...
+test_run:cmd("switch trig_master")
+---
+- true
+...
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- simple ctl_event_case
+ctl_const = box.ctl_event.const()
+---
+...
+test_run:cmd("setopt delimiter ';'")
+---
+- true
+...
+function on_replace(old, new)
+    return box.tuple.new({new[1], new[2], 'M'})
+end;
+---
+...
+function on_ctl_trig(event)
+    if event.type == ctl_const.SPACE and
+       event.action == ctl_const.SPACE_CREATE then
+        local space = box.space[event.space_id]
+        space:before_replace(on_replace)
+    end
+end;
+---
+...
+test_run:cmd("setopt delimiter ''");
+---
+- true
+...
+active_trig = box.ctl_event.on_ctl_event(on_ctl_trig)
+---
+...
+t1 = box.schema.space.create('trig1')
+---
+...
+_ = t1:create_index('pk')
+---
+...
+t1:replace({1, 2})
+---
+- [1, 2, 'M']
+...
+t1:select()
+---
+- - [1, 2, 'M']
+...
+-- clear the trigger
+box.ctl_event.on_ctl_event(nil, active_trig)
+---
+...
+t2 = box.schema.space.create('trig2')
+---
+...
+_ = t2:create_index('pk')
+---
+...
+t2:replace({1, 2})
+---
+- [1, 2]
+...
+t2:select()
+---
+- - [1, 2]
+...
+test_run:cmd("push filter 'replica: [-0-9a-f]+' to 'server: <master-uuid>'")
+---
+- true
+...
+test_run:cmd("push filter 'space_id: [0-9]+' to 'space_id: <space_id>'")
+---
+- true
+...
+-- create replica and test bootstrap events
+box.schema.user.grant('guest', 'replication', nil, nil, {if_not_exists = 
true})
+---
+...
+test_run:cmd("create server trig_replica with rpl_master=trig_master, 
script='box/lua/trig_replica.lua'")
+---
+- true
+...
+test_run:cmd("start server trig_replica")
+---
+- true
+...
+t1:replace({2, 3})
+---
+- [2, 3, 'M']
+...
+t1:select()
+---
+- - [1, 2, 'M']
+  - [2, 3, 'M']
+...
+test_run:cmd("switch trig_replica")
+---
+- true
+...
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- list all events
+events
+---
+- - type: 4
+    server: <master-uuid>
+    status: 1
+  - type: 1
+    status: 8
+  - type: 4
+    server: <master-uuid>
+    status: 2
+  - type: 4
+    server: <master-uuid>
+    status: 4
+  - type: 4
+    server: <master-uuid>
+    status: 5
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 1
+    status: 9
+  - type: 4
+    server: <master-uuid>
+    status: 6
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 1
+    status: 10
+  - type: 4
+    server: <master-uuid>
+    status: 7
+  - type: 4
+    server: <master-uuid>
+    status: 4
+  - type: 4
+    server: <master-uuid>
+    status: 8
+  - type: 4
+    server: <master-uuid>
+    status: 9
+...
+-- check before replace trigger
+box.space.trig1:select()
+---
+- - [1, 2, 'M', 'R']
+  - [2, 3, 'M', 'R']
+...
+test_run:cmd("switch trig_master")
+---
+- true
+...
+test_run:cmd("stop server trig_replica")
+---
+- true
+...
+t1:replace({3, 4})
+---
+- [3, 4, 'M']
+...
+test_run:cmd("start server trig_replica")
+---
+- true
+...
+test_run:cmd("switch trig_replica")
+---
+- true
+...
+env = require('test_run')
+---
+...
+test_run = env.new()
+---
+...
+-- list all events
+events
+---
+- - type: 1
+    status: 1
+  - type: 4
+    server: <master-uuid>
+    status: 1
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 2
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 2
+    action: 1
+    space_id: <space_id>
+  - type: 1
+    status: 2
+  - type: 1
+    status: 5
+  - type: 4
+    server: <master-uuid>
+    status: 2
+  - type: 4
+    server: <master-uuid>
+    status: 4
+  - type: 4
+    server: <master-uuid>
+    status: 8
+  - type: 4
+    server: <master-uuid>
+    status: 9
+...
+-- check tuples changed only one time
+box.space.trig1:select()
+---
+- - [1, 2, 'M', 'R']
+  - [2, 3, 'M', 'R']
+  - [3, 4, 'M', 'R']
+...
+test_run:cmd("switch default")
+---
+- true
+...
+test_run:cmd("stop server trig_replica")
+---
+- true
+...
+test_run:cmd("stop server trig_master")
+---
+- true
+...
diff --git a/test/box/ctl_event.test.lua b/test/box/ctl_event.test.lua
new file mode 100644
index 000000000..8cb97d80f
--- /dev/null
+++ b/test/box/ctl_event.test.lua
@@ -0,0 +1,76 @@
+env = require('test_run')
+test_run = env.new()
+
+-- create master instance
+test_run:cmd("create server trig_master with script='box/lua/
trig_master.lua'")
+test_run:cmd("start server trig_master")
+test_run:cmd("switch trig_master")
+env = require('test_run')
+test_run = env.new()
+
+-- simple ctl_event_case
+ctl_const = box.ctl_event.const()
+test_run:cmd("setopt delimiter ';'")
+function on_replace(old, new)
+    return box.tuple.new({new[1], new[2], 'M'})
+end;
+function on_ctl_trig(event)
+    if event.type == ctl_const.SPACE and
+       event.action == ctl_const.SPACE_CREATE then
+        local space = box.space[event.space_id]
+        space:before_replace(on_replace)
+    end
+end;
+test_run:cmd("setopt delimiter ''");
+active_trig = box.ctl_event.on_ctl_event(on_ctl_trig)
+t1 = box.schema.space.create('trig1')
+_ = t1:create_index('pk')
+t1:replace({1, 2})
+t1:select()
+
+-- clear the trigger
+box.ctl_event.on_ctl_event(nil, active_trig)
+t2 = box.schema.space.create('trig2')
+_ = t2:create_index('pk')
+t2:replace({1, 2})
+t2:select()
+
+test_run:cmd("push filter 'replica: [-0-9a-f]+' to 'server: <master-uuid>'")
+test_run:cmd("push filter 'space_id: [0-9]+' to 'space_id: <space_id>'")
+
+-- create replica and test bootstrap events
+box.schema.user.grant('guest', 'replication', nil, nil, {if_not_exists = 
true})
+test_run:cmd("create server trig_replica with rpl_master=trig_master, 
script='box/lua/trig_replica.lua'")
+test_run:cmd("start server trig_replica")
+t1:replace({2, 3})
+t1:select()
+
+test_run:cmd("switch trig_replica")
+env = require('test_run')
+test_run = env.new()
+
+-- list all events
+events
+
+-- check before replace trigger
+box.space.trig1:select()
+
+test_run:cmd("switch trig_master")
+test_run:cmd("stop server trig_replica")
+
+t1:replace({3, 4})
+
+test_run:cmd("start server trig_replica")
+
+test_run:cmd("switch trig_replica")
+env = require('test_run')
+test_run = env.new()
+
+-- list all events
+events
+
+-- check tuples changed only one time
+box.space.trig1:select()
+test_run:cmd("switch default")
+test_run:cmd("stop server trig_replica")
+test_run:cmd("stop server trig_master")
diff --git a/test/box/lua/trig_master.lua b/test/box/lua/trig_master.lua
new file mode 100644
index 000000000..fa253ba4f
--- /dev/null
+++ b/test/box/lua/trig_master.lua
@@ -0,0 +1,8 @@
+#!/usr/bin/env tarantool
+require('console').listen(os.getenv('ADMIN'))
+
+box.cfg({
+    listen              = os.getenv("LISTEN"),
+    memtx_memory        = 107374182,
+    replication_connect_timeout = 0.5,
+})
diff --git a/test/box/lua/trig_replica.lua b/test/box/lua/trig_replica.lua
new file mode 100644
index 000000000..581bdfdb7
--- /dev/null
+++ b/test/box/lua/trig_replica.lua
@@ -0,0 +1,46 @@
+#!/usr/bin/env tarantool
+require('console').listen(os.getenv('ADMIN'))
+
+events = {}
+
+local ctl_const = box.ctl_event.const()
+local recovery_status = nil
+
+local function before_replace(old, new)
+    if recovery_status == ctl_const.RECOVERY_SNAPSHOT_START or
+       recovery_status == ctl_const.RECOVERY_SNAPSHOT_DONE then
+       -- local files
+       return new
+    end
+    if new == nil then
+        return new
+    end
+    local k = {new:unpack()}
+    table.insert(k, 'R')
+    return box.tuple.new(k)
+end
+
+
+local function ctl_event_trigger(event)
+    -- register the event
+    table.insert(events, event)
+    if event.type == ctl_const.RECOVERY then
+        recovery_status = event.status
+    end
+    if event.type == ctl_const.SPACE and
+       event.action == ctl_const.SPACE_CREATE then
+        if event.space_id > 511 then
+            local space = box.space[event.space_id]
+            space:before_replace(before_replace)
+        end
+    end
+end
+
+box.ctl_event.on_ctl_event(ctl_event_trigger)
+
+box.cfg({
+    listen              = os.getenv("LISTEN"),
+    replication         = os.getenv("MASTER"),
+    memtx_memory        = 107374182,
+    replication_connect_timeout = 0.5,
+})
diff --git a/test/box/misc.result b/test/box/misc.result
index e213d7964..969081e59 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -62,6 +62,7 @@ t
   - cfg
   - commit
   - ctl
+  - ctl_event
   - error
   - feedback
   - index

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [tarantool-patches] [PATCH 2/2] On ctl event trigger
  2018-08-30 12:38   ` Vladimir Davydov
  2018-08-30 13:04     ` Georgy Kirichenko
@ 2018-08-30 13:21     ` Vladimir Davydov
  2018-08-30 14:45       ` [tarantool-patches] " Konstantin Osipov
  2018-08-30 14:40     ` Konstantin Osipov
  2 siblings, 1 reply; 15+ messages in thread
From: Vladimir Davydov @ 2018-08-30 13:21 UTC (permalink / raw)
  To: Georgy Kirichenko; +Cc: tarantool-patches

On Thu, Aug 30, 2018 at 03:38:07PM +0300, Vladimir Davydov wrote:
> On Tue, Aug 28, 2018 at 07:19:13PM +0300, Georgy Kirichenko wrote:
> > Introduce a ctl event trigger fired in cases of a bootstrap/recovery status
> > changes, a space create/alter/drop action, an applier state change and
> > shutdown. Trigger could be set with box.ctl_event even before the first
> > box.cfg invocation to control recovery and bootstrap behavior.
> > 
> > Event constants accessible via box.ctl_event.const()
> > There are events:
> >   - RECOVERY
> >   - SPACE
> >   - SHUTDOWN
> >   - APPLIER
> > 
> > A recovery event might have a status:
> >    * RECOVERY_SNAPSHOT_START
> >    * RECOVERY_SNAPSHOT_DONE
> >    * RECOVERY_HOT_STANDBY_START
> >    * RECOVERY_HOT_STANDBY_DONE
> >    * RECOVERY_XLOGS_DONE
> >    * RECOVERY_BOOTSTRAP_START
> >    * RECOVERY_BOOTSTRAP_DONE
> >    * RECOVERY_INITIAL_JOIN_START
> >    * RECOVERY_INITIAL_JOIN_DONE
> >    * RECOVERY_FINAL_JOIN_DONE
> > 
> > A space event consists of space identifier and action:
> >    * SPACE_CREATE
> >    * SPACE_ALTER
> >    * SPACE_DELETE
> > 
> > An applier event contains peer uuid and state:
> >    * APPLIER_OFF
> >    * APPLIER_CONNECT
> >    * APPLIER_CONNECTED
> >    * APPLIER_AUTH
> >    * APPLIER_READY
> >    * APPLIER_INITIAL_JOIN
> >    * APPLIER_FINAL_JOIN
> >    * APPLIER_JOINED
> >    * APPLIER_SYNC
> >    * APPLIER_FOLLOW
> >    * APPLIER_STOPPED
> >    * APPLIER_DISCONNECTED
> >    * APPLIER_LOADING
> 
> I can't review this patch properly, because ctl_event.[hc] are missing,
> but I've a few notes about the API.
> 
> First, I don't understand why you need two kinds of constants. I mean
> SPACE and SPACE_CREATE, or RECOVERY and RECOVERY_SNAPSHOT_START. IMO
> there should be just one set of constants.
> 
> Second, IMO the event trigger should be installed with box.ctl.on_event
> (not box.ctl_event.on_ctl_event), and constants should be defined in
> box.ctl.event (not box.ctl_event.const()).
> 
> Third, let's start with fewer events, only ones that we need to right
> now (e.g. for replication conflict resolution with before_replace).
> We can add other events later, when we need to.

Another thought. I think that mixing space, applier, and global events
in the same trigger isn't a good idea. I'd prefer box.ctl.on_event (or
whatever it's going to be called) to be called only on global events,
such as recovery completion or shutdown. Probably, it shouldn't be
passed any context apart from the event type, because I can't think why
it would ever need it (if it needs anything it can always get it from
box.info).

For applier events, I'd prefer to have a separate trigger, which would
only be called whenever an applier state changes. It should be passed
applier id in the arguments - everything else it can retrieve from
box.info.

Space events look rather superfluous to me, because we already can track
creation of system objects with box space.on_replace. True, we can't
install it before recovery is complete, but we could introduce a global
event called on completion of recovery of all system spaces instead.
We could use it to install before_replace trigger for conflict
resolution.

Anyway, this topic cries for a discussion/brainstorming IMO.

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [tarantool-patches] Re: [PATCH 2/2] On ctl event trigger
  2018-08-30 12:38   ` Vladimir Davydov
  2018-08-30 13:04     ` Georgy Kirichenko
  2018-08-30 13:21     ` Vladimir Davydov
@ 2018-08-30 14:40     ` Konstantin Osipov
  2 siblings, 0 replies; 15+ messages in thread
From: Konstantin Osipov @ 2018-08-30 14:40 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Georgy Kirichenko

* Vladimir Davydov <vdavydov.dev@gmail.com> [18/08/30 15:39]:
> Second, IMO the event trigger should be installed with box.ctl.on_event
> (not box.ctl_event.on_ctl_event), and constants should be defined in
> box.ctl.event (not box.ctl_event.const()).

I agree with this name change.


-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [tarantool-patches] Re: [PATCH 2/2] On ctl event trigger
  2018-08-30 13:21     ` Vladimir Davydov
@ 2018-08-30 14:45       ` Konstantin Osipov
  0 siblings, 0 replies; 15+ messages in thread
From: Konstantin Osipov @ 2018-08-30 14:45 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Georgy Kirichenko

* Vladimir Davydov <vdavydov.dev@gmail.com> [18/08/30 16:25]:
> Another thought. I think that mixing space, applier, and global events
> in the same trigger isn't a good idea. I'd prefer box.ctl.on_event (or
> whatever it's going to be called) to be called only on global events,
> such as recovery completion or shutdown. Probably, it shouldn't be
> passed any context apart from the event type, because I can't think why
> it would ever need it (if it needs anything it can always get it from
> box.info).

What you're saying is that you should be able to subscribe to
only certain events. If you want to say, set a hook on shutdown
event, this hook shouldn't be handling other events. 
am I getting this right?

> 
> For applier events, I'd prefer to have a separate trigger, which would
> only be called whenever an applier state changes. It should be passed
> applier id in the arguments - everything else it can retrieve from
> box.info.

I disagree with voiding the trigger context. This creates a state
dependency between the trigger and the rest of the world - i.e.
when the trigger is really invoked the environment may change. I
think it's convenient to capture key event properties in a context
and pass it into the event. 

Put it differently, I don' tthink events are any different than
tarantool triggers. The only difference perhaps is that with ctl
api we provide a handy way to *subscribe* to any event from a
single place - i.e. it's like a single entry point to all events
we have, a message bus so to speak.

> Space events look rather superfluous to me, because we already can track
> creation of system objects with box space.on_replace. True, we can't
> install it before recovery is complete, but we could introduce a global
> event called on completion of recovery of all system spaces instead.
> We could use it to install before_replace trigger for conflict
> resolution.

Seems reasonable, I agree.
 
> Anyway, this topic cries for a discussion/brainstorming IMO.

-- 
Konstantin Osipov, Moscow, Russia, +7 903 626 22 32
http://tarantool.io - www.twitter.com/kostja_osipov

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [tarantool-patches] Re: [PATCH 1/2] Update lua space cache just after creation
  2018-08-30 12:31   ` Konstantin Osipov
@ 2018-08-31  4:53     ` Georgy Kirichenko
  0 siblings, 0 replies; 15+ messages in thread
From: Georgy Kirichenko @ 2018-08-31  4:53 UTC (permalink / raw)
  To: Konstantin Osipov; +Cc: tarantool-patches

[-- Attachment #1: Type: text/plain, Size: 2689 bytes --]

On Thursday, August 30, 2018 3:31:07 PM MSK Konstantin Osipov wrote:

> We can well use txn->on_commit trigger for this purpose, having
> set an on_commit trigger which pushes on_ctl events. This trigger
> is invoked *after* a record is written to wal in txn.cc. Why do
> you need to manipulate with the internal alter.cc triggers at all?
> Why is the order of invocation of these triggers important?
No, we can't, because generally there may be a space writes between space
 was created and space was commited. It is only current implementation issue  
that index creation acquires a ddl lock and stops applying.
> 
> Ideally, your on_ctl trigger should be invoked after all the alter
> triggers ahve been invoked. For that, all you need to do is to
> ensure that on_ctl trigger is added *after* the alter trigger -
> i.e. it has a lower priority/order than alter trigger. Is your
> problem with trigger ordering? Is it that you've been trying to
> add alter trigger *after* you added the on_ctl trigger, and, since
> triggers are normally added to the end of the list, when on_ctl
> trigger was invoked the space was not yet available in box.space?
My problem I can't see lua space object for a created space while on_replace 
fires because on_replace trigger fires at operation stage and lua cache updates 
only after commit - this is the issue. This can't be solved with trigger 
ordering cos triggers are running on different stages and the only way i see 
is to move space cache update trigger to the operation stage.
> 
> If it is the case, then I think we should simply add
> trigger_add_first() function, and use this function to add alter
> triggers to the beginning of the list.
First we should change stage of lua cache updates, that the patch does.
> 
> That would solve the problem until we have more strict ordering
> requirements, at which point we could add trigger->order member
> and trigger_add_with_order() method.
> 
> Finally, I don't see how the test case is testing the order. I
> believe the test case should pass both before and after the patch.
Test uses fiber scheduling mechanism, the first fiber creates the second one that 
creates a space and yields on wal. The first fiber checks that corresponding 
space objects is visible (the second fiber still waiting for a wal).
> 
> > @@ -1571,3 +1571,64 @@ fio = require('fio')
> > 
> >  box.space.test:drop()
> >  ---
> >  ...
> > 
> > +-- allocate a space id to prevent max space id update
> 
> Why is test description missing in the result file?
> 
> 
> Nitpick: please add leading -- and trailing -- to the test
> description, start it with a capital letter and end with a dot.
> 
> 
> Thanks for the patch,


[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [tarantool-patches] Re: [PATCH 1/2] Update lua space cache just after creation
  2018-08-30 12:06   ` Vladimir Davydov
@ 2018-08-31  4:57     ` Georgy Kirichenko
  0 siblings, 0 replies; 15+ messages in thread
From: Georgy Kirichenko @ 2018-08-31  4:57 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladimir Davydov

[-- Attachment #1: Type: text/plain, Size: 1073 bytes --]

> > 
> > +
> > +
> > +-- allocate a space id to prevent max space id update
> 
> Why?
If max space id should be updated then space creates in two commits - the first 
one update max space id and the second one - creates space. And then I can't 
schedule fibers to get execution when space is created but not yet commited.
> 
> > +trig = box.schema.space.create('trig')
> > +trig_id = trig.id
> > +trig:drop()
> > +trig = nil
> > +fiber = require('fiber')
> > +ch = fiber.channel(1)
> > +errinj = box.error.injection
> > +test_run:cmd("setopt delimiter ';'")
> > +-- check space exists just after creation
> > +errinj.set("ERRINJ_WAL_WRITE", true);
> > +_ = fiber.create(function ()
> > +        fiber.create(function ()
> > +            pcall(box.schema.space.create, 'trig', {id = trig_id})
> > +            ch:put(true)
> > +        end)
> > +        trig = box.space.trig
> > +    end);
> > +trig ~= nil;
> > +ch:get();
> > +--and not exists after rollback
> > +box.space.trig;
> > +test_run:cmd("setopt delimiter ''");
> > +
> > +errinj.set("ERRINJ_WAL_WRITE", false)


[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2018-08-31  4:57 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-08-28 16:19 [tarantool-patches] [PATCH 0/2] Box control event trigger Georgy Kirichenko
2018-08-28 16:19 ` [tarantool-patches] [PATCH 1/2] Update lua space cache just after creation Georgy Kirichenko
2018-08-30 12:06   ` Vladimir Davydov
2018-08-31  4:57     ` [tarantool-patches] " Georgy Kirichenko
2018-08-30 12:31   ` Konstantin Osipov
2018-08-31  4:53     ` Georgy Kirichenko
2018-08-28 16:19 ` [tarantool-patches] [PATCH 2/2] On ctl event trigger Georgy Kirichenko
2018-08-30 12:07   ` Vladimir Davydov
2018-08-30 12:10   ` Vladimir Davydov
2018-08-30 12:38   ` Vladimir Davydov
2018-08-30 13:04     ` Georgy Kirichenko
2018-08-30 13:21     ` Vladimir Davydov
2018-08-30 14:45       ` [tarantool-patches] " Konstantin Osipov
2018-08-30 14:40     ` Konstantin Osipov
2018-08-30 12:50   ` Konstantin Osipov

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox