强制执行
支持实时事务的关键在于数据库运行时能够安全地中断当前事务的执行。有两种方法可用:通过异步事件处理程序,或者通过传递给数据库运行时的应用程序回调函数,该函数在事务执行期间定期被调用,向应用程序发出已达到截止控制点的信号。
回调方法
首先让我们来研究一下回调方法。当诸如系统定时器或硬件看门狗之类的异步原语不可用时,通常会使用回调方法。应用程序常常会轮询系统时钟或响应硬件中断等。要使用回调,应用程序需要向数据库运行时注册一个回调函数。SmartEDB 运行时提供了一种标准方法来注册各种回调(为清晰起见,此处使用伪代码):
mco_db_register_callback():
typedef MCO_RET (*mco_db_callback_t)(mco_db_h db);
MCO_RET mco_db_register_callback(mco_db_h db, mco_db_callback_t callback);
接下来的步骤是回调实现本身。数据库运行时要求,如果事务可以继续运行,回调函数应返回“成功”返回码(MCO_S_OK);如果事务必须中止,则返回 MCO_E_INTERRUPTED 返回码。请注意,回调函数会被频繁调用,因此其实现应尽可能“轻量”,少占用系统资源(就像中断服务例程的实现一样)。
/* read the current time and establish the transaction control point */
control_point = MCO_SYSTEM_GET_CURRENT_TIME_MSEC() + deadline / 2;
...
static MCO_RET check_deadline(mco_db_h db)
{
mco_trans_h t;
/* obtain a pointer to the currently running transaction */
if (mco_trans_get_current(db, &t) == MCO_S_OK) {
/* check whether the transaction control point was reached */
if (control_point < MCO_SYSTEM_GET_CURRENT_TIME_MSEC()) {
/* yes, return error code to indicate that */
return MCO_E_INTERRUPTED;
}
}
/*
* the control point has not been reached yet
* the transaction can continue running
*/
return MCO_S_OK;
}
下一步是启动实时事务。
mco_trans_rt_start(db, ...., ¶ms /* deadline */, ... &t);
{
.....
/*
* transaction workload
*/
rc = transaction_ workload();
/*
* If any error including MCO_E_INTERRUPTED was detected, during the transaction,
* the following commit call rolls back the transaction. In other words, even an
* attempt to commit a transaction will abort the transaction if the transaction
* was in an error state.
*/
rc = mco_trans_commit(t);
}
定时器方法
使用基于定时器的事务控制方法的第一步是确定事务控制点。如前所述,将控制点设置为截止期限间隔的一半通常是安全的(如前所述,这种措施可能过于粗糙,会影响错过/满足截止期限的比例)。然后应用程序启动定时器,将定时器周期设置为第一步确定的控制点。安装定时器是操作系统特定的,可能简单也可能复杂。例如,FreeRTOS 的语义在伪代码中表示为:
static mco_db_h connection;
.....
void set_sys_timer(sys_timer_t *timer, mco_db_h db, mco_interval_t deadline)
{
......
xTimerCreate( "exdb-timer", pdMS_TO_TICKS(deadline), pdFALSE, TimerProc );
}
请注意“安全”的截止间隔时间,以及当定时器的周期到期时,会执行 TimerProc 定时器回调函数。还要注意定时器回调是在定时器服务任务的上下文中执行的。因此,定时器回调函数绝不能尝试阻塞。与回调方法一样,定时器函数必须设置 MCO_E_INTERRUPTED 错误代码,以向应用程序表明事务已到达控制点。在这种情况下,MCO_E_INTERRUPTED 是通过函数 mco_trans_set_error() 设置的,而不是通过设置返回代码。
void TimerProc( TimerHandle_t xTimer )
{
mco_trans_h t;
if (mco_trans_get_current(connection, &t) == MCO_S_OK) {
mco_trans_set_error(t, MCO_E_INTERRUPTED);
}
}
最后,启动一个事务:
set_sys_timer(&timer, db, deadline/2);
mco_trans_rt_start(db, ...., ¶ms /* deadline */, ... &t);
......
rc = transaction_workload();
/*
* If any error including MCO_E_INTERRUPTED was detected, the following
* commit request rolls back the transaction
*/
rc = mco_trans_commit(t);
请注意,与回调不同,定时器处理程序仅在定时器到期时调用一次,然后设置事务错误标志。数据库运行时会定期以原子操作的方式验证该标志。