//
// Copyright (c) 2010-2022 Antmicro
//
// This file is licensed under the MIT License.
// Full license text is available in 'licenses/MIT.txt'.
//
using System;
using System.Threading;
using Antmicro.Migrant;
using Antmicro.Renode.Core;
using Antmicro.Renode.Logging;
using Antmicro.Renode.Utilities;
using Antmicro.Renode.Debugging;
namespace Antmicro.Renode.Time
{
///
/// Represents an intermediate entity that distribute interval granted from master time sources to other sinks.
///
public class SlaveTimeSource : TimeSourceBase, ITimeSource, ITimeSink, IDisposable
{
///
/// Creates a new instance of .
///
public SlaveTimeSource()
{
locker = new object();
TimePassed += HandleTimePassed;
}
///
/// Disposes this instance.
///
public override void Dispose()
{
this.Trace("Disposing...");
base.Dispose();
base.Stop();
lock(locker)
{
TimeHandle?.Dispose();
}
StopDispatcher();
this.Trace("Disposed");
}
///
/// Pauses the execution of the time source. It can be resumed using method.
///
public void Pause()
{
lock(locker)
{
this.Trace("Pausing...");
if(!isStarted)
{
this.Trace();
return;
}
if(isPaused)
{
this.Trace();
return;
}
RequestStop();
using(sync.HighPriority)
{
stopwatch.Stop();
isPaused = true;
DeactivateSlavesSourceSide();
}
this.Trace("Paused");
}
}
///
/// Resumes execution of the time source.
///
public void Resume()
{
this.Trace("Resuming...");
lock(locker)
{
using(sync.HighPriority)
{
ActivateSlavesSourceSide();
isPaused = false;
stopwatch.Start();
}
}
this.Trace("Resumed");
}
///
///
/// If this slave is not attached to any master time source, the domain is null.
///
public override ITimeDomain Domain { get { return timeHandle?.TimeSource.Domain; } }
///
///
/// If this time source is already connected to a master, old handle is disposed before accepting the new one.
///
public TimeHandle TimeHandle
{
get
{
return timeHandle;
}
set
{
lock(locker)
{
StopDispatcher();
TimeHandle?.Dispose();
this.Trace("About to attach to the new master");
timeHandle = value;
timeHandle.PauseRequested += RequestStop;
timeHandle.StartRequested += HandleStartRequest;
ResetVirtualTime(timeHandle.TotalElapsedTime);
StartDispatcher();
}
}
}
private void HandleStartRequest()
{
this.Trace();
lock(locker)
{
this.Trace();
if(dispatcherThread == null)
{
this.Trace();
// if the dispatcher is not started yet - start it
StartDispatcher();
}
else
{
this.Trace();
// if the dispatcher is already running - set the restart flag
dispatcherStartRequested = true;
}
}
}
///
/// Provides the implementation of time-distribution among slaves.
///
private void Dispatch()
{
var isLocked = false;
try
{
#if DEBUG
using(this.TraceRegion("Dispatcher loop"))
#endif
using(this.ObtainSourceActiveState())
using(this.ObtainSinkActiveState())
using(TimeDomainsManager.Instance.RegisterCurrentThread(() => new TimeStamp(TimeHandle.TimeSource.NearestSyncPoint, TimeHandle.TimeSource.Domain)))
{
while(true)
{
try
{
while(isStarted)
{
WaitIfBlocked();
if(!DispatchInner())
{
break;
}
}
}
catch(Exception e)
{
this.Trace(LogLevel.Error, $"Got an exception: {e.Message} {e.StackTrace}");
throw;
}
lock(locker)
{
if(!dispatcherStartRequested)
{
this.Trace();
// the `locker` is re-acquired here to
// make sure that dispose-related code of all usings
// is executed before setting `dispatcherThread` to
// null (what allows to start new dispatcher thread);
// otherwise there could be a race condition when
// new thread enters usings (e.g., activates source side)
// and then the old one exits them (deactivating source
// side as a result)
Monitor.Enter(locker, ref isLocked);
break;
}
dispatcherStartRequested = false;
this.Trace();
}
}
}
}
finally
{
dispatcherThread = null;
if(isLocked)
{
this.Trace();
Monitor.Exit(locker);
}
this.Trace();
}
}
private bool DispatchInner()
{
if(!TimeHandle.RequestTimeInterval(out var intervalGranted))
{
this.Trace("Time interval request interrupted");
return false;
}
if(isPaused)
{
this.Trace("Handle paused");
TimeHandle.ReportBackAndBreak(intervalGranted);
return true;
}
var quantum = Quantum;
this.Trace($"Current QUANTUM is {quantum.Ticks} ticks");
var timeLeft = intervalGranted;
while(waitingForSlave || (timeLeft >= quantum && isStarted))
{
waitingForSlave = false;
var syncPointReached = InnerExecute(out var elapsed);
timeLeft -= elapsed;
if(!syncPointReached)
{
// we should not ask for time grant since the current one is not finished yet
waitingForSlave = true;
TimeHandle.ReportBackAndBreak(timeLeft);
return true;
}
}
TimeHandle.ReportBackAndContinue(timeLeft);
return true;
}
private void StartDispatcher()
{
lock(locker)
{
if(dispatcherThread != null || TimeHandle == null)
{
return;
}
isStarted = true;
dispatcherThread = new Thread(Dispatch) { IsBackground = true, Name = "SlaveTimeSource Dispatcher Thread" };
dispatcherThread.Start();
}
}
private void StopDispatcher()
{
Thread threadToJoin = null;
lock(locker)
{
isStarted = false;
if(dispatcherThread != null && Thread.CurrentThread.ManagedThreadId != dispatcherThread.ManagedThreadId)
{
threadToJoin = dispatcherThread;
}
}
threadToJoin?.Join();
}
private void HandleTimePassed(TimeInterval diff)
{
TimeHandle?.ReportProgress(diff);
}
[Transient]
private Thread dispatcherThread;
private TimeHandle timeHandle;
private bool waitingForSlave;
private bool dispatcherStartRequested;
private readonly object locker;
}
}