Introduce transaction_timeout

This commit adds timeout that is expected to be used as a prevention
of long-running queries. Any session within the transaction will be
terminated after spanning longer than this timeout.

However, this timeout is not applied to prepared transactions.
Only transactions with user connections are affected.

Discussion: https://postgr.es/m/CAAhFRxiQsRs2Eq5kCo9nXE3HTugsAAJdSQSmxncivebAxdmBjQ%40mail.gmail.com
Author: Andrey Borodin <amborodin@acm.org>
Author: Japin Li <japinli@hotmail.com>
Author: Junwang Zhao <zhjwpku@gmail.com>
Reviewed-by: Nikolay Samokhvalov <samokhvalov@gmail.com>
Reviewed-by: Andres Freund <andres@anarazel.de>
Reviewed-by: Fujii Masao <masao.fujii@oss.nttdata.com>
Reviewed-by: bt23nguyent <bt23nguyent@oss.nttdata.com>
Reviewed-by: Yuhang Qiu <iamqyh@gmail.com>
This commit is contained in:
Alexander Korotkov 2024-02-15 23:34:11 +02:00
parent 5c9f2f9398
commit 51efe38cb9
23 changed files with 343 additions and 5 deletions

View File

@ -9140,6 +9140,42 @@ COPY postgres_log FROM '/full/path/to/logfile.csv' WITH csv;
</listitem>
</varlistentry>
<varlistentry id="guc-transaction-timeout" xreflabel="transaction_timeout">
<term><varname>transaction_timeout</varname> (<type>integer</type>)
<indexterm>
<primary><varname>transaction_timeout</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
<para>
Terminate any session that spans longer than the specified amount of
time in the transaction. The limit applies both to explicit transactions
(started with <command>BEGIN</command>) and to an implicitly started
transaction corresponding to a single statement.
If this value is specified without units, it is taken as milliseconds.
A value of zero (the default) disables the timeout.
</para>
<para>
If <varname>transaction_timeout</varname> is shorter or equal to
<varname>idle_in_transaction_session_timeout</varname> or <varname>statement_timeout</varname>
<varname>transaction_timeout</varname> will invalidate the longer timeout.
</para>
<para>
Setting <varname>transaction_timeout</varname> in
<filename>postgresql.conf</filename> is not recommended because it would
affect all sessions.
</para>
<note>
<para>
Prepared transactions are not subject to this timeout.
</para>
</note>
</listitem>
</varlistentry>
<varlistentry id="guc-lock-timeout" xreflabel="lock_timeout">
<term><varname>lock_timeout</varname> (<type>integer</type>)
<indexterm>

View File

@ -2139,6 +2139,10 @@ StartTransaction(void)
*/
s->state = TRANS_INPROGRESS;
/* Schedule transaction timeout */
if (TransactionTimeout > 0)
enable_timeout_after(TRANSACTION_TIMEOUT, TransactionTimeout);
ShowTransactionState("StartTransaction");
}

View File

@ -586,6 +586,7 @@ AutoVacLauncherMain(int argc, char *argv[])
* regular maintenance from being executed.
*/
SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
SetConfigOption("transaction_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
SetConfigOption("lock_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
SetConfigOption("idle_in_transaction_session_timeout", "0",
PGC_SUSET, PGC_S_OVERRIDE);
@ -1587,6 +1588,7 @@ AutoVacWorkerMain(int argc, char *argv[])
* regular maintenance from being executed.
*/
SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
SetConfigOption("transaction_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
SetConfigOption("lock_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE);
SetConfigOption("idle_in_transaction_session_timeout", "0",
PGC_SUSET, PGC_S_OVERRIDE);

View File

@ -59,6 +59,7 @@ int DeadlockTimeout = 1000;
int StatementTimeout = 0;
int LockTimeout = 0;
int IdleInTransactionSessionTimeout = 0;
int TransactionTimeout = 0;
int IdleSessionTimeout = 0;
bool log_lock_waits = false;

View File

@ -3418,6 +3418,17 @@ ProcessInterrupts(void)
IdleInTransactionSessionTimeoutPending = false;
}
if (TransactionTimeoutPending)
{
/* As above, ignore the signal if the GUC has been reset to zero. */
if (TransactionTimeout > 0)
ereport(FATAL,
(errcode(ERRCODE_TRANSACTION_TIMEOUT),
errmsg("terminating connection due to transaction timeout")));
else
TransactionTimeoutPending = false;
}
if (IdleSessionTimeoutPending)
{
/* As above, ignore the signal if the GUC has been reset to zero. */
@ -3632,6 +3643,15 @@ check_log_stats(bool *newval, void **extra, GucSource source)
return true;
}
/* GUC assign hook for transaction_timeout */
void
assign_transaction_timeout(int newval, void *extra)
{
if (TransactionTimeout <= 0 &&
get_timeout_active(TRANSACTION_TIMEOUT))
disable_timeout(TRANSACTION_TIMEOUT, false);
}
/*
* set_debug_options --- apply "-d N" command line option
@ -4483,12 +4503,18 @@ PostgresMain(const char *dbname, const char *username)
pgstat_report_activity(STATE_IDLEINTRANSACTION_ABORTED, NULL);
/* Start the idle-in-transaction timer */
if (IdleInTransactionSessionTimeout > 0)
if (IdleInTransactionSessionTimeout > 0
&& (IdleInTransactionSessionTimeout < TransactionTimeout || TransactionTimeout == 0))
{
idle_in_transaction_timeout_enabled = true;
enable_timeout_after(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
IdleInTransactionSessionTimeout);
}
/* Schedule or reschedule transaction timeout */
if (TransactionTimeout > 0 && !get_timeout_active(TRANSACTION_TIMEOUT))
enable_timeout_after(TRANSACTION_TIMEOUT,
TransactionTimeout);
}
else if (IsTransactionOrTransactionBlock())
{
@ -4496,12 +4522,18 @@ PostgresMain(const char *dbname, const char *username)
pgstat_report_activity(STATE_IDLEINTRANSACTION, NULL);
/* Start the idle-in-transaction timer */
if (IdleInTransactionSessionTimeout > 0)
if (IdleInTransactionSessionTimeout > 0
&& (IdleInTransactionSessionTimeout < TransactionTimeout || TransactionTimeout == 0))
{
idle_in_transaction_timeout_enabled = true;
enable_timeout_after(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
IdleInTransactionSessionTimeout);
}
/* Schedule or reschedule transaction timeout */
if (TransactionTimeout > 0 && !get_timeout_active(TRANSACTION_TIMEOUT))
enable_timeout_after(TRANSACTION_TIMEOUT,
TransactionTimeout);
}
else
{
@ -4554,6 +4586,13 @@ PostgresMain(const char *dbname, const char *username)
enable_timeout_after(IDLE_SESSION_TIMEOUT,
IdleSessionTimeout);
}
/*
* If GUC is changed then it's handled in
* assign_transaction_timeout().
*/
if (TransactionTimeout > 0 && get_timeout_active(TRANSACTION_TIMEOUT))
disable_timeout(TRANSACTION_TIMEOUT, false);
}
/* Report any recently-changed GUC options */
@ -5112,7 +5151,8 @@ enable_statement_timeout(void)
/* must be within an xact */
Assert(xact_started);
if (StatementTimeout > 0)
if (StatementTimeout > 0
&& (StatementTimeout < TransactionTimeout || TransactionTimeout == 0))
{
if (!get_timeout_active(STATEMENT_TIMEOUT))
enable_timeout_after(STATEMENT_TIMEOUT, StatementTimeout);

View File

@ -252,6 +252,7 @@ Section: Class 25 - Invalid Transaction State
25P01 E ERRCODE_NO_ACTIVE_SQL_TRANSACTION no_active_sql_transaction
25P02 E ERRCODE_IN_FAILED_SQL_TRANSACTION in_failed_sql_transaction
25P03 E ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT idle_in_transaction_session_timeout
25P04 E ERRCODE_TRANSACTION_TIMEOUT transaction_timeout
Section: Class 26 - Invalid SQL Statement Name

View File

@ -33,6 +33,7 @@ volatile sig_atomic_t ProcDiePending = false;
volatile sig_atomic_t CheckClientConnectionPending = false;
volatile sig_atomic_t ClientConnectionLost = false;
volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
volatile sig_atomic_t TransactionTimeoutPending = false;
volatile sig_atomic_t IdleSessionTimeoutPending = false;
volatile sig_atomic_t ProcSignalBarrierPending = false;
volatile sig_atomic_t LogMemoryContextPending = false;

View File

@ -75,6 +75,7 @@ static void ShutdownPostgres(int code, Datum arg);
static void StatementTimeoutHandler(void);
static void LockTimeoutHandler(void);
static void IdleInTransactionSessionTimeoutHandler(void);
static void TransactionTimeoutHandler(void);
static void IdleSessionTimeoutHandler(void);
static void IdleStatsUpdateTimeoutHandler(void);
static void ClientCheckTimeoutHandler(void);
@ -764,6 +765,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
RegisterTimeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
IdleInTransactionSessionTimeoutHandler);
RegisterTimeout(TRANSACTION_TIMEOUT, TransactionTimeoutHandler);
RegisterTimeout(IDLE_SESSION_TIMEOUT, IdleSessionTimeoutHandler);
RegisterTimeout(CLIENT_CONNECTION_CHECK_TIMEOUT, ClientCheckTimeoutHandler);
RegisterTimeout(IDLE_STATS_UPDATE_TIMEOUT,
@ -1395,6 +1397,14 @@ LockTimeoutHandler(void)
kill(MyProcPid, SIGINT);
}
static void
TransactionTimeoutHandler(void)
{
TransactionTimeoutPending = true;
InterruptPending = true;
SetLatch(MyLatch);
}
static void
IdleInTransactionSessionTimeoutHandler(void)
{

View File

@ -2577,6 +2577,17 @@ struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
{
{"transaction_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
gettext_noop("Sets the maximum allowed time in a transaction with a session (not a prepared transaction)."),
gettext_noop("A value of 0 turns off the timeout."),
GUC_UNIT_MS
},
&TransactionTimeout,
0, 0, INT_MAX,
NULL, assign_transaction_timeout, NULL
},
{
{"idle_session_timeout", PGC_USERSET, CLIENT_CONN_STATEMENT,
gettext_noop("Sets the maximum allowed idle time between queries, when not in a transaction."),

View File

@ -701,6 +701,7 @@
#default_transaction_deferrable = off
#session_replication_role = 'origin'
#statement_timeout = 0 # in milliseconds, 0 is disabled
#transaction_timeout = 0 # in milliseconds, 0 is disabled
#lock_timeout = 0 # in milliseconds, 0 is disabled
#idle_in_transaction_session_timeout = 0 # in milliseconds, 0 is disabled
#idle_session_timeout = 0 # in milliseconds, 0 is disabled

View File

@ -3115,6 +3115,7 @@ _doSetFixedOutputState(ArchiveHandle *AH)
ahprintf(AH, "SET statement_timeout = 0;\n");
ahprintf(AH, "SET lock_timeout = 0;\n");
ahprintf(AH, "SET idle_in_transaction_session_timeout = 0;\n");
ahprintf(AH, "SET transaction_timeout = 0;\n");
/* Select the correct character set encoding */
ahprintf(AH, "SET client_encoding = '%s';\n",

View File

@ -1252,6 +1252,8 @@ setup_connection(Archive *AH, const char *dumpencoding,
ExecuteSqlStatement(AH, "SET lock_timeout = 0");
if (AH->remoteVersion >= 90600)
ExecuteSqlStatement(AH, "SET idle_in_transaction_session_timeout = 0");
if (AH->remoteVersion >= 170000)
ExecuteSqlStatement(AH, "SET transaction_timeout = 0");
/*
* Quote all identifiers, if requested.

View File

@ -117,6 +117,7 @@ init_libpq_conn(PGconn *conn)
run_simple_command(conn, "SET statement_timeout = 0");
run_simple_command(conn, "SET lock_timeout = 0");
run_simple_command(conn, "SET idle_in_transaction_session_timeout = 0");
run_simple_command(conn, "SET transaction_timeout = 0");
/*
* we don't intend to do any updates, put the connection in read-only mode

View File

@ -91,6 +91,7 @@ extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
extern PGDLLIMPORT volatile sig_atomic_t TransactionTimeoutPending;
extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending;
extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
extern PGDLLIMPORT volatile sig_atomic_t LogMemoryContextPending;

View File

@ -429,6 +429,7 @@ extern PGDLLIMPORT int DeadlockTimeout;
extern PGDLLIMPORT int StatementTimeout;
extern PGDLLIMPORT int LockTimeout;
extern PGDLLIMPORT int IdleInTransactionSessionTimeout;
extern PGDLLIMPORT int TransactionTimeout;
extern PGDLLIMPORT int IdleSessionTimeout;
extern PGDLLIMPORT bool log_lock_waits;

View File

@ -155,6 +155,7 @@ extern void assign_timezone_abbreviations(const char *newval, void *extra);
extern bool check_transaction_deferrable(bool *newval, void **extra, GucSource source);
extern bool check_transaction_isolation(int *newval, void **extra, GucSource source);
extern bool check_transaction_read_only(bool *newval, void **extra, GucSource source);
extern void assign_transaction_timeout(int newval, void *extra);
extern const char *show_unix_socket_permissions(void);
extern bool check_wal_buffers(int *newval, void **extra, GucSource source);
extern bool check_wal_consistency_checking(char **newval, void **extra,

View File

@ -31,6 +31,7 @@ typedef enum TimeoutId
STANDBY_TIMEOUT,
STANDBY_LOCK_TIMEOUT,
IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
TRANSACTION_TIMEOUT,
IDLE_SESSION_TIMEOUT,
IDLE_STATS_UPDATE_TIMEOUT,
CLIENT_CONNECTION_CHECK_TIMEOUT,

View File

@ -72,3 +72,6 @@ installcheck-prepared-txns: all temp-install
check-prepared-txns: all temp-install
$(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic
check-timeouts: all temp-install
$(pg_isolation_regress_check) timeouts timeouts-long

View File

@ -0,0 +1,69 @@
Parsed test spec with 3 sessions
starting permutation: s7_begin s7_sleep s7_commit_and_chain s7_sleep s7_check s7_abort
step s7_begin:
BEGIN ISOLATION LEVEL READ COMMITTED;
SET transaction_timeout = '1s';
step s7_sleep: SELECT pg_sleep(0.6);
pg_sleep
--------
(1 row)
step s7_commit_and_chain: COMMIT AND CHAIN;
step s7_sleep: SELECT pg_sleep(0.6);
pg_sleep
--------
(1 row)
step s7_check: SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s7';
count
-----
0
(1 row)
step s7_abort: ABORT;
starting permutation: s8_begin s8_sleep s8_select_1 s8_check checker_sleep checker_sleep s8_check
step s8_begin:
BEGIN ISOLATION LEVEL READ COMMITTED;
SET transaction_timeout = '900ms';
step s8_sleep: SELECT pg_sleep(0.6);
pg_sleep
--------
(1 row)
step s8_select_1: SELECT 1;
?column?
--------
1
(1 row)
step s8_check: SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s8';
count
-----
0
(1 row)
step checker_sleep: SELECT pg_sleep(0.3);
pg_sleep
--------
(1 row)
step checker_sleep: SELECT pg_sleep(0.3);
pg_sleep
--------
(1 row)
step s8_check: SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s8';
count
-----
0
(1 row)

View File

@ -1,4 +1,4 @@
Parsed test spec with 2 sessions
Parsed test spec with 7 sessions
starting permutation: rdtbl sto locktbl
step rdtbl: SELECT * FROM accounts;
@ -79,3 +79,80 @@ step slto: SET lock_timeout = '10s'; SET statement_timeout = '10ms';
step update: DELETE FROM accounts WHERE accountid = 'checking'; <waiting ...>
step update: <... completed>
ERROR: canceling statement due to statement timeout
starting permutation: stto s3_begin s3_sleep s3_check s3_abort
step stto: SET statement_timeout = '10ms'; SET transaction_timeout = '1s';
step s3_begin: BEGIN ISOLATION LEVEL READ COMMITTED;
step s3_sleep: SELECT pg_sleep(0.1);
ERROR: canceling statement due to statement timeout
step s3_check: SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s3';
count
-----
1
(1 row)
step s3_abort: ABORT;
starting permutation: tsto s3_begin checker_sleep s3_check
step tsto: SET statement_timeout = '1s'; SET transaction_timeout = '10ms';
step s3_begin: BEGIN ISOLATION LEVEL READ COMMITTED;
step checker_sleep: SELECT pg_sleep(0.1);
pg_sleep
--------
(1 row)
step s3_check: SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s3';
count
-----
0
(1 row)
starting permutation: itto s4_begin checker_sleep s4_check
step itto: SET idle_in_transaction_session_timeout = '10ms'; SET transaction_timeout = '1s';
step s4_begin: BEGIN ISOLATION LEVEL READ COMMITTED;
step checker_sleep: SELECT pg_sleep(0.1);
pg_sleep
--------
(1 row)
step s4_check: SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s4';
count
-----
0
(1 row)
starting permutation: tito s5_begin checker_sleep s5_check
step tito: SET idle_in_transaction_session_timeout = '1s'; SET transaction_timeout = '10ms';
step s5_begin: BEGIN ISOLATION LEVEL READ COMMITTED;
step checker_sleep: SELECT pg_sleep(0.1);
pg_sleep
--------
(1 row)
step s5_check: SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s5';
count
-----
0
(1 row)
starting permutation: s6_begin s6_tt checker_sleep s6_check
step s6_begin: BEGIN ISOLATION LEVEL READ COMMITTED;
step s6_tt: SET statement_timeout = '1s'; SET transaction_timeout = '10ms';
step checker_sleep: SELECT pg_sleep(0.1);
pg_sleep
--------
(1 row)
step s6_check: SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s6';
count
-----
0
(1 row)

View File

@ -89,6 +89,7 @@ test: sequence-ddl
test: async-notify
test: vacuum-no-cleanup-lock
test: timeouts
test: timeouts-long
test: vacuum-concurrent-drop
test: vacuum-conflict
test: vacuum-skip-locked

View File

@ -0,0 +1,35 @@
# Tests for transaction timeout that require long wait times
session s7
step s7_begin
{
BEGIN ISOLATION LEVEL READ COMMITTED;
SET transaction_timeout = '1s';
}
step s7_commit_and_chain { COMMIT AND CHAIN; }
step s7_sleep { SELECT pg_sleep(0.6); }
step s7_abort { ABORT; }
session s8
step s8_begin
{
BEGIN ISOLATION LEVEL READ COMMITTED;
SET transaction_timeout = '900ms';
}
# to test that quick query does not restart transaction_timeout
step s8_select_1 { SELECT 1; }
step s8_sleep { SELECT pg_sleep(0.6); }
session checker
step checker_sleep { SELECT pg_sleep(0.3); }
step s7_check { SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s7'; }
step s8_check { SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s8'; }
# COMMIT AND CHAIN must restart transaction timeout
permutation s7_begin s7_sleep s7_commit_and_chain s7_sleep s7_check s7_abort
# transaction timeout expires in presence of query flow, session s7 FATAL-out
# this relatevely long sleeps are picked to ensure 300ms gap between check and timeouts firing
# expected flow: timeouts is scheduled after s8_begin and fires approximately after checker_sleep (300ms before check)
# possible buggy flow: timeout is schedules after s8_select_1 and fires 300ms after s8_check
# to ensure this 300ms gap we need minimum transaction_timeout of 300ms
permutation s8_begin s8_sleep s8_select_1 s8_check checker_sleep checker_sleep s8_check

View File

@ -1,4 +1,4 @@
# Simple tests for statement_timeout and lock_timeout features
# Simple tests for statement_timeout, lock_timeout and transaction_timeout features
setup
{
@ -27,6 +27,33 @@ step locktbl { LOCK TABLE accounts; }
step update { DELETE FROM accounts WHERE accountid = 'checking'; }
teardown { ABORT; }
session s3
step s3_begin { BEGIN ISOLATION LEVEL READ COMMITTED; }
step stto { SET statement_timeout = '10ms'; SET transaction_timeout = '1s'; }
step tsto { SET statement_timeout = '1s'; SET transaction_timeout = '10ms'; }
step s3_sleep { SELECT pg_sleep(0.1); }
step s3_abort { ABORT; }
session s4
step s4_begin { BEGIN ISOLATION LEVEL READ COMMITTED; }
step itto { SET idle_in_transaction_session_timeout = '10ms'; SET transaction_timeout = '1s'; }
session s5
step s5_begin { BEGIN ISOLATION LEVEL READ COMMITTED; }
step tito { SET idle_in_transaction_session_timeout = '1s'; SET transaction_timeout = '10ms'; }
session s6
step s6_begin { BEGIN ISOLATION LEVEL READ COMMITTED; }
step s6_tt { SET statement_timeout = '1s'; SET transaction_timeout = '10ms'; }
session checker
step checker_sleep { SELECT pg_sleep(0.1); }
step s3_check { SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s3'; }
step s4_check { SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s4'; }
step s5_check { SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s5'; }
step s6_check { SELECT count(*) FROM pg_stat_activity WHERE application_name = 'isolation/timeouts/s6'; }
# It's possible that the isolation tester will not observe the final
# steps as "waiting", thanks to the relatively short timeouts we use.
# We can ensure consistent test output by marking those steps with (*).
@ -47,3 +74,14 @@ permutation wrtbl lto update(*)
permutation wrtbl lsto update(*)
# statement timeout expires first, row-level lock
permutation wrtbl slto update(*)
# statement timeout expires first
permutation stto s3_begin s3_sleep s3_check s3_abort
# transaction timeout expires first, session s3 FATAL-out
permutation tsto s3_begin checker_sleep s3_check
# idle in transaction timeout expires first, session s4 FATAL-out
permutation itto s4_begin checker_sleep s4_check
# transaction timeout expires first, session s5 FATAL-out
permutation tito s5_begin checker_sleep s5_check
# transaction timeout can be schedule amid transaction, session s6 FATAL-out
permutation s6_begin s6_tt checker_sleep s6_check