FPPS4/sys/kern/kern_timeout.pas

456 lines
9.4 KiB
Plaintext

unit kern_timeout;
{$mode ObjFPC}{$H+}
{$CALLING SysV_ABI_CDecl}
interface
uses
mqueue,
kern_synch,
sys_sleepqueue,
kern_thr,
kern_mtx,
kern_rwlock,
kern_callout;
type
t_callout_func=kern_callout.t_callout_func;
p_callout=kern_callout.p_callout;
t_callout=kern_callout.t_callout;
const
CALLOUT_LOCAL_ALLOC =kern_callout.CALLOUT_LOCAL_ALLOC ;
CALLOUT_ACTIVE =kern_callout.CALLOUT_ACTIVE ;
CALLOUT_PENDING =kern_callout.CALLOUT_PENDING ;
CALLOUT_MPSAFE =kern_callout.CALLOUT_MPSAFE ;
CALLOUT_RETURNUNLOCKED=kern_callout.CALLOUT_RETURNUNLOCKED;
CALLOUT_SHAREDLOCK =kern_callout.CALLOUT_SHAREDLOCK ;
CALLOUT_RWLOCK =kern_callout.CALLOUT_RWLOCK ;
procedure kern_timeout_init();
procedure CC_LOCK(cc:p_callout_cpu);
procedure CC_UNLOCK(cc:p_callout_cpu);
procedure CC_LOCK_ASSERT(cc:p_callout_cpu);
function callout_lock():p_callout_cpu;
procedure callout_cc_del(c:p_callout;cc:p_callout_cpu);
procedure callout_init (c:p_callout;mpsafe:Integer);
procedure _callout_init_lock(c:p_callout;lock:Pointer;flags:Integer);
procedure softclock_call_cc(c:p_callout;cc:p_callout_cpu);
function _callout_stop_safe(c:p_callout;safe:Integer):Integer;
function callout_reset_on(c:p_callout;to_ticks:Int64;ftn:t_callout_func;arg:Pointer):Integer;
function callout_drain(c:p_callout):Integer;
function callout_stop(c:p_callout):Integer;
procedure callout_init_mtx(c:p_callout;var mtx:mtx;flags:Integer);
procedure callout_init_rw(c:p_callout;var rw:Pointer;flags:Integer);
function callout_reset(c:p_callout;on_tick:Int64;fn,arg:Pointer):Integer;
function callout_reset_curcpu(c:p_callout;on_tick:Int64;fn,arg:Pointer):Integer;
implementation
uses
md_timeout;
var
timeout_cpu:t_callout_cpu;
rw_giant:Pointer=nil;
procedure callout_cpu_init(cc:p_callout_cpu);
begin
mtx_init(cc^.cc_lock,'callout');
TAILQ_INIT(@cc^.cc_calllist);
TAILQ_INIT(@cc^.cc_callfree);
end;
procedure kern_timeout_init();
begin
callout_cpu_init(@timeout_cpu);
md_start_softclock();
end;
procedure callout_init(c:p_callout;mpsafe:Integer);
begin
c^:=Default(t_callout);
if (mpsafe<>0) then
begin
c^.c_lock :=nil; //no locking
c^.c_flags:=CALLOUT_RETURNUNLOCKED;
end else
begin
c^.c_lock :=@rw_giant;
c^.c_flags:=CALLOUT_RWLOCK;
end;
end;
procedure _callout_init_lock(c:p_callout;lock:Pointer;flags:Integer);
begin
c^:=Default(t_callout);
c^.c_lock:=lock;
Assert((flags and (not (CALLOUT_RETURNUNLOCKED or CALLOUT_SHAREDLOCK)))=0,'callout_init_lock: bad flags');
Assert((lock<>nil) or ((flags and CALLOUT_RETURNUNLOCKED)=0),'callout_init_lock: CALLOUT_RETURNUNLOCKED with no lock');
Assert(lock<>nil,'invalid lock');
c^.c_flags:=flags and (CALLOUT_RETURNUNLOCKED or CALLOUT_SHAREDLOCK);
end;
procedure CC_LOCK(cc:p_callout_cpu); inline;
begin
mtx_lock(cc^.cc_lock);
end;
procedure CC_UNLOCK(cc:p_callout_cpu); inline;
begin
mtx_unlock(cc^.cc_lock);
end;
procedure CC_LOCK_ASSERT(cc:p_callout_cpu); inline;
begin
mtx_assert(cc^.cc_lock);
end;
function callout_lock():p_callout_cpu; inline;
begin
Result:=@timeout_cpu;
CC_LOCK(Result);
end;
procedure callout_cc_add(c:p_callout;cc:p_callout_cpu;to_ticks:Int64;func:t_callout_func;arg:Pointer);
begin
CC_LOCK_ASSERT(cc);
if (to_ticks <= 0) then
begin
to_ticks:=1;
end;
c^.c_arg:=arg;
c^.c_flags:=c^.c_flags or (CALLOUT_ACTIVE or CALLOUT_PENDING);
c^.c_func:=func;
c^.c_time:=to_ticks;
TAILQ_INSERT_TAIL(@cc^.cc_calllist,c,@c^.c_links);
md_callout_new_inserted(c,cc);
end;
procedure callout_cc_del(c:p_callout;cc:p_callout_cpu);
begin
md_callout_done(c);
if ((c^.c_flags and CALLOUT_LOCAL_ALLOC)=0) then Exit;
c^.c_func:=nil;
TAILQ_INSERT_TAIL(@cc^.cc_callfree,c,@c^.c_links);
end;
procedure lc_lock(c:p_callout);
var
c_lock :Pointer;
c_flags:QWORD;
begin
c_lock :=c^.c_lock;
c_flags:=c^.c_flags;
if (c_lock=nil) then Exit;
if ((c_flags and CALLOUT_RWLOCK)<>0) then
begin
if ((c_flags and CALLOUT_SHAREDLOCK)<>0) then
begin
rw_rlock(PPointer(c_lock)^);
end else
begin
rw_wlock(PPointer(c_lock)^);
end;
end else
begin
mtx_lock(p_mtx(c_lock)^);
end;
end;
procedure lc_unlock(c:p_callout);
var
c_lock :Pointer;
c_flags:QWORD;
begin
c_lock :=c^.c_lock;
c_flags:=c^.c_flags;
if (c_lock=nil) then Exit;
if ((c_flags and CALLOUT_RWLOCK)<>0) then
begin
if ((c_flags and CALLOUT_SHAREDLOCK)<>0) then
begin
rw_runlock(PPointer(c_lock)^);
end else
begin
rw_wunlock(PPointer(c_lock)^);
end;
end else
begin
mtx_unlock(p_mtx(c_lock)^);
end;
end;
procedure softclock_call_cc(c:p_callout;cc:p_callout_cpu);
label
skip;
var
c_arg :Pointer;
c_func :t_callout_func;
c_lock :Pointer;
c_flags:QWORD;
begin
Assert((c^.c_flags and (CALLOUT_PENDING or CALLOUT_ACTIVE))=(CALLOUT_PENDING or CALLOUT_ACTIVE),'softclock_call_cc');
c_lock :=c^.c_lock;
c_func :=c^.c_func;
c_arg :=c^.c_arg;
c_flags:=c^.c_flags;
if ((c^.c_flags and CALLOUT_LOCAL_ALLOC)<>0) then
c^.c_flags:=CALLOUT_LOCAL_ALLOC
else
c^.c_flags:=c^.c_flags and (not CALLOUT_PENDING);
cc^.cc_curr :=c;
cc^.cc_cancel:=0;
CC_UNLOCK(cc);
if (c_lock<>nil) then
begin
lc_lock(c);
if (cc^.cc_cancel<>0) then
begin
lc_unlock(c);
goto skip;
end;
cc^.cc_cancel:=1;
end;
THREAD_NO_SLEEPING();
c_func(c_arg);
THREAD_SLEEPING_OK();
if ((c_flags and CALLOUT_RETURNUNLOCKED)=0) then
begin
lc_unlock(c);
end;
skip:
CC_LOCK(cc);
Assert(cc^.cc_curr=c,'mishandled cc_curr');
cc^.cc_curr:=nil;
if (cc^.cc_waiting<>0) then
begin
cc^.cc_waiting:=0;
CC_UNLOCK(cc);
wakeup(@cc^.cc_waiting);
CC_LOCK(cc);
end;
Assert(((c_flags and CALLOUT_LOCAL_ALLOC)=0) or (c^.c_flags=CALLOUT_LOCAL_ALLOC),'corrupted callout');
if ((c_flags and CALLOUT_LOCAL_ALLOC)<>0) then
begin
callout_cc_del(c, cc);
end;
end;
{
* New interface; clients allocate their own callout structures.
*
* callout_reset() - establish or change a timeout
* callout_stop() - disestablish a timeout
* callout_init() - initialize a callout structure so that it can
* safely be passed to callout_reset() and callout_stop()
*
* defines three convenience macros:
*
* callout_active() - returns truth if callout has not been stopped,
* drained, or deactivated since the last time the callout was
* reset.
* callout_pending() - returns truth if callout is still waiting for timeout
* callout_deactivate() - marks the callout as having been serviced
}
function callout_reset_on(c:p_callout;to_ticks:Int64;ftn:t_callout_func;arg:Pointer):Integer;
var
cc:p_callout_cpu;
cancelled:Integer;
begin
cancelled:=0;
cc:=callout_lock();
md_callout_reset(c);
if (cc^.cc_curr=c) then
begin
{
* We're being asked to reschedule a callout which is
* currently in progress. If there is a lock then we
* can cancel the callout if it has not really started.
}
if (c^.c_lock<>nil) and (cc^.cc_cancel=0) then
begin
cancelled:=1;
cc^.cc_cancel:=1;
end;
if (cc^.cc_waiting<>0) then
begin
{
* Someone has called callout_drain to kill this
* callout. Don't reschedule.
}
CC_UNLOCK(cc);
Exit(cancelled);
end;
end;
if ((c^.c_flags and CALLOUT_PENDING)<>0) then
begin
TAILQ_REMOVE(@cc^.cc_calllist,c,@c^.c_links);
cancelled:=1;
c^.c_flags:=c^.c_flags and (not (CALLOUT_ACTIVE or CALLOUT_PENDING));
end;
callout_cc_add(c, cc, to_ticks, ftn, arg);
CC_UNLOCK(cc);
Exit(cancelled);
end;
function _callout_stop_safe(c:p_callout;safe:Integer):Integer;
label
again;
var
cc:p_callout_cpu;
use_lock :Integer;
sq_locked:Integer;
begin
sq_locked:=0;
use_lock:=ord((safe=0) and (c^.c_lock<>nil));
again:
cc:=callout_lock();
md_callout_stop(c);
if ((c^.c_flags and CALLOUT_PENDING)=0) then
begin
c^.c_flags:=c^.c_flags and (not CALLOUT_ACTIVE);
if (cc^.cc_curr<>c) then
begin
CC_UNLOCK(cc);
if (sq_locked<>0) then
begin
sleepq_release(@cc^.cc_waiting);
end;
Exit(0);
end;
if (safe<>0) and (not THREAD_IS_NOSLEEPING) then
begin
while (cc^.cc_curr=c) do
begin
if (sq_locked=0) then
begin
CC_UNLOCK(cc);
sleepq_lock(@cc^.cc_waiting);
sq_locked:=1;
goto again;
end;
cc^.cc_waiting:=1;
CC_UNLOCK(cc);
sleepq_add(@cc^.cc_waiting,nil,PChar('codrain'),SLEEPQ_SLEEP,0);
sleepq_wait(@cc^.cc_waiting,0);
sq_locked:=0;
CC_LOCK(cc);
end;
end else
if (use_lock<>0) and (cc^.cc_cancel=0) then
begin
cc^.cc_cancel:=1;
CC_UNLOCK(cc);
Assert(sq_locked=0,'sleepqueue chain locked');
Exit(1);
end;
CC_UNLOCK(cc);
Assert(sq_locked=0,'sleepqueue chain still locked');
Exit(0);
end;
if (sq_locked<>0) then
begin
sleepq_release(@cc^.cc_waiting);
end;
c^.c_flags:=c^.c_flags and (not (CALLOUT_ACTIVE or CALLOUT_PENDING));
TAILQ_REMOVE(@cc^.cc_calllist,c,@c^.c_links);
callout_cc_del(c, cc);
CC_UNLOCK(cc);
Exit(1);
end;
//
function callout_drain(c:p_callout):Integer;
begin
Result:=_callout_stop_safe(c,1);
end;
function callout_stop(c:p_callout):Integer;
begin
Result:=_callout_stop_safe(c,0);
end;
procedure callout_init_mtx(c:p_callout;var mtx:mtx;flags:Integer);
begin
_callout_init_lock(c,@mtx,flags);
end;
procedure callout_init_rw(c:p_callout;var rw:Pointer;flags:Integer);
begin
_callout_init_lock(c,@rw,flags or CALLOUT_RWLOCK);
end;
function callout_reset(c:p_callout;on_tick:Int64;fn,arg:Pointer):Integer;
begin
Result:=callout_reset_on(c,on_tick,t_callout_func(fn),arg);
end;
function callout_reset_curcpu(c:p_callout;on_tick:Int64;fn,arg:Pointer):Integer;
begin
Result:=callout_reset_on(c,on_tick,t_callout_func(fn),arg);
end;
end.