First of all, a little bit on strategy when dealing with consensus. Only deal with the basic primitives that you have at hand which are the sequential log, the client, the state machine and commands. Whenever your solution needs to reason about who the leader is you are bound to be doing the wrong thing. The only thing you should have to reason about are the operations in the sequential log which you know are completely ordered.
Here is a rough sketch for how a solution working only with the basic primitives could look like:
LeaseRequest (command)
seq_no
owner
start_time
lease_time
LeaseSM (state machine)
if ( seq_no != sm_seq_no + 1 )
{
return;
}
sm_seq_no = seq_no;
if ( owner != me )
{
// assume worst case for other lease span (longest span)
sm_lease_tmo = max( sm_lease_tmo, current_time() + lease_time );
}
else
{
// assume worst case for own lease span (from time request was sent)
sm_lease_tmo = start_time + lease_time;
}
sm_owner = owner;
Client (lease monitor and potentially holder)
if ( sm_owner == me )
{
if ( sm_lease_tmo + REAFFIRM_DELTA > current_time() )
{
// resend request to reaffirm lease
raft.send( new Operation( sm_seq_no + 1, me, current_time(), LEASE_TIME ) );
}
}
else if ( sm_lease_tmo > current_time() )
{
// nobody seems to have a lease, send request for lease
raft.send( new Operation( sm_seq_no + 1, me, current_time(), LEASE_TIME ) );
}
The client code above can be interpreted as running continuously, but of course a real implementation should set up timers.
What is happening above is that the client is monitoring the state machine for an active current lease and if it detects that a current lease is not active then it sends a request for a lease and makes sure to base it upon the latest information by utilising a sequence number, basically functioning as a fence, barrier or whatever you want to call it. The request encodes the start_time which is guaranteed to be earlier than any time that any other consensus group member is able to observe your lease request. When another member observes your request they assume that it is has the worst case validity from their perspective, the full time of the lease (lease_time) from the time they processed the message. Those members will respect the expiry of that full worst case timeout before attempting to request a lease of their own. This should guarantee that there never is an overlap in leases. Note that other members do not care about the start_time, it is merely a convenient way for the client to keep track of the time it sent its own lease request and on a successful lease request calculate its worst case allowed validity from that point in time.
I am sure I have made some mistake somewhere and at the very least forgotten about some edge case, but this should give you a general idea of how a proper solution can be created on top of Raft and the Lamport state machine approach, rather than by trying to dip deep into Raft and "hooking into" some internal aspects of it.