Methods To Enforce Deadlines

The key to supporting real-time transactions is the ability for the database runtime to safely interrupt the execution of the current transaction. Two methods are available: through an asynchronous event handler or via an application callback that is passed to the database runtime and is invoked periodically during a transaction, signaling the application that the deadline control point was reached.

The Callback Method

Let us examine the callback method first. The callback method is normally employed when asynchronous primitives such as a system timer or a hardware watchdog are unavailable. Often the application polls a system clock or responds to hardware interrupts, etc. To use a callback, the application registers a callback function with the database runtime. The eXtremeDB runtime provides a standard method for registering various callbacks (pseudo-code is used for clarity):

    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);
	

The next step is the callback implementation itself. The database runtime requires that the callback returns a ‘success’ return code (MCO_S_OK) if the transaction can continue running and MCO_E_INTERRUPTED return code if the transaction must be aborted. Note that the callback is invoked often, so its implementation should be as “light” as possible on using system resources (like the implementation of interrupt service routines).

    /* 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;
    }
			

The next step is to start the real-time transaction

    mco_trans_rt_start(db, ...., &params /*  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);
    }
			

 

 

The Timer Method

The first step in using the timer-based transaction control method is to determine the transaction control point. As discussed, setting the control point to half of the deadline interval is often safe (as discussed earlier this measure could be too rough and hurt the miss/meet deadline ratio). Then the application starts the timer, setting the timer period to the control point determined in the first step. Installing a timer is operating system-specific and could be simple or complicated. For example, FreeRTOS semantics in pseudo-code:

    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 );
    }
			

Note the “safe” deadline interval, and that the TimerProc timer callback function is executed when the timer’s period expires. Also note that the timer callback executes in the context of the timer service task. It is therefore essential that timer callback functions never attempt to block. Like the callback method, the timer function must set the MCO_E_INTERRUPTED error code, indicating to the application that the transaction has reached the control point. In this case, MCO_E_INTERRUPTED is set by the function mco_trans_set_error(), not by setting the return code.

    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);
         }
    }
			

Finally, a transaction is started:

    set_sys_timer(&timer, db, deadline/2);

    mco_trans_rt_start(db, ...., &params /*  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);
			

Note that in contrast to the callback, the timer handler is invoked just once, when the timer expires and then the transaction error flag is set. The database runtime periodically verifies the flag in an atomic operation.