You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Zero/ZeroLevel/Services/FASTER/Epochs/LightEpoch.cs

450 lines
14 KiB

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Diagnostics;
namespace FASTER.core
{
/// <summary>
/// Epoch protection
/// </summary>
public unsafe class LightEpoch
{
/// <summary>
/// Default invalid index entry.
/// </summary>
private const int kInvalidIndex = 0;
/// <summary>
/// Default number of entries in the entries table
/// </summary>
public const int kTableSize = 128;
/// <summary>
/// Default drainlist size
/// </summary>
private const int kDrainListSize = 16;
/// <summary>
/// Thread protection status entries.
/// </summary>
private Entry[] tableRaw;
private GCHandle tableHandle;
private Entry* tableAligned;
private static Entry[] threadIndex;
private static GCHandle threadIndexHandle;
private static Entry* threadIndexAligned;
/// <summary>
/// List of action, epoch pairs containing actions to performed
/// when an epoch becomes safe to reclaim.
/// </summary>
private int drainCount = 0;
private readonly EpochActionPair[] drainList = new EpochActionPair[kDrainListSize];
/// <summary>
/// A thread's entry in the epoch table.
/// </summary>
[ThreadStatic]
private static int threadEntryIndex;
/// <summary>
/// Number of instances using this entry
/// </summary>
[ThreadStatic]
private static int threadEntryIndexCount;
[ThreadStatic]
static int threadId;
/// <summary>
/// Global current epoch value
/// </summary>
public int CurrentEpoch;
/// <summary>
/// Cached value of latest epoch that is safe to reclaim
/// </summary>
public int SafeToReclaimEpoch;
/// <summary>
/// Static constructor to setup shared cache-aligned space
/// to store per-entry count of instances using that entry
/// </summary>
static LightEpoch()
{
// Over-allocate to do cache-line alignment
threadIndex = new Entry[kTableSize + 2];
threadIndexHandle = GCHandle.Alloc(threadIndex, GCHandleType.Pinned);
long p = (long)threadIndexHandle.AddrOfPinnedObject();
// Force the pointer to align to 64-byte boundaries
long p2 = (p + (Constants.kCacheLineBytes - 1)) & ~(Constants.kCacheLineBytes - 1);
threadIndexAligned = (Entry*)p2;
}
/// <summary>
/// Instantiate the epoch table
/// </summary>
public LightEpoch()
{
// Over-allocate to do cache-line alignment
tableRaw = new Entry[kTableSize + 2];
tableHandle = GCHandle.Alloc(tableRaw, GCHandleType.Pinned);
long p = (long)tableHandle.AddrOfPinnedObject();
// Force the pointer to align to 64-byte boundaries
long p2 = (p + (Constants.kCacheLineBytes - 1)) & ~(Constants.kCacheLineBytes - 1);
tableAligned = (Entry*)p2;
CurrentEpoch = 1;
SafeToReclaimEpoch = 0;
for (int i = 0; i < kDrainListSize; i++)
drainList[i].epoch = int.MaxValue;
drainCount = 0;
}
/// <summary>
/// Clean up epoch table
/// </summary>
public void Dispose()
{
tableHandle.Free();
tableAligned = null;
tableRaw = null;
CurrentEpoch = 1;
SafeToReclaimEpoch = 0;
}
/// <summary>
/// Check whether current thread is protected
/// </summary>
/// <returns>Result of the check</returns>
public bool IsProtected()
{
return kInvalidIndex != threadEntryIndex;
}
/// <summary>
/// Enter the thread into the protected code region
/// </summary>
/// <returns>Current epoch</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int ProtectAndDrain()
{
int entry = threadEntryIndex;
(*(tableAligned + entry)).threadId = threadEntryIndex;
(*(tableAligned + entry)).localCurrentEpoch = CurrentEpoch;
if (drainCount > 0)
{
Drain((*(tableAligned + entry)).localCurrentEpoch);
}
return (*(tableAligned + entry)).localCurrentEpoch;
}
/// <summary>
/// Check and invoke trigger actions that are ready
/// </summary>
/// <param name="nextEpoch">Next epoch</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Drain(int nextEpoch)
{
ComputeNewSafeToReclaimEpoch(nextEpoch);
for (int i = 0; i < kDrainListSize; i++)
{
var trigger_epoch = drainList[i].epoch;
if (trigger_epoch <= SafeToReclaimEpoch)
{
if (Interlocked.CompareExchange(ref drainList[i].epoch, int.MaxValue - 1, trigger_epoch) == trigger_epoch)
{
var trigger_action = drainList[i].action;
drainList[i].action = null;
drainList[i].epoch = int.MaxValue;
trigger_action();
if (Interlocked.Decrement(ref drainCount) == 0) break;
}
}
}
}
/// <summary>
/// Thread acquires its epoch entry
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Acquire()
{
if (threadEntryIndex == kInvalidIndex)
threadEntryIndex = ReserveEntryForThread();
threadEntryIndexCount++;
}
/// <summary>
/// Thread releases its epoch entry
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Release()
{
int entry = threadEntryIndex;
(*(tableAligned + entry)).localCurrentEpoch = 0;
(*(tableAligned + entry)).threadId = 0;
threadEntryIndexCount--;
if (threadEntryIndexCount == 0)
{
(threadIndexAligned + threadEntryIndex)->threadId = 0;
threadEntryIndex = kInvalidIndex;
}
}
/// <summary>
/// Thread suspends its epoch entry
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Suspend()
{
Release();
}
/// <summary>
/// Thread resumes its epoch entry
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Resume()
{
Acquire();
ProtectAndDrain();
}
/// <summary>
/// Increment global current epoch
/// </summary>
/// <returns></returns>
public int BumpCurrentEpoch()
{
int nextEpoch = Interlocked.Add(ref CurrentEpoch, 1);
if (drainCount > 0)
Drain(nextEpoch);
return nextEpoch;
}
/// <summary>
/// Increment current epoch and associate trigger action
/// with the prior epoch
/// </summary>
/// <param name="onDrain">Trigger action</param>
/// <returns></returns>
public int BumpCurrentEpoch(Action onDrain)
{
int PriorEpoch = BumpCurrentEpoch() - 1;
int i = 0, j = 0;
while (true)
{
if (drainList[i].epoch == int.MaxValue)
{
if (Interlocked.CompareExchange(ref drainList[i].epoch, int.MaxValue - 1, int.MaxValue) == int.MaxValue)
{
drainList[i].action = onDrain;
drainList[i].epoch = PriorEpoch;
Interlocked.Increment(ref drainCount);
break;
}
}
else
{
var triggerEpoch = drainList[i].epoch;
if (triggerEpoch <= SafeToReclaimEpoch)
{
if (Interlocked.CompareExchange(ref drainList[i].epoch, int.MaxValue - 1, triggerEpoch) == triggerEpoch)
{
var triggerAction = drainList[i].action;
drainList[i].action = onDrain;
drainList[i].epoch = PriorEpoch;
triggerAction();
break;
}
}
}
if (++i == kDrainListSize)
{
ProtectAndDrain();
i = 0;
if (++j == 500)
{
j = 0;
Debug.WriteLine("Delay finding a free entry in the drain list");
}
}
}
ProtectAndDrain();
return PriorEpoch + 1;
}
/// <summary>
/// Looks at all threads and return the latest safe epoch
/// </summary>
/// <param name="currentEpoch">Current epoch</param>
/// <returns>Safe epoch</returns>
private int ComputeNewSafeToReclaimEpoch(int currentEpoch)
{
int oldestOngoingCall = currentEpoch;
for (int index = 1; index <= kTableSize; ++index)
{
int entry_epoch = (*(tableAligned + index)).localCurrentEpoch;
if (0 != entry_epoch)
{
if (entry_epoch < oldestOngoingCall)
{
oldestOngoingCall = entry_epoch;
}
}
}
// The latest safe epoch is the one just before
// the earliest unsafe epoch.
SafeToReclaimEpoch = oldestOngoingCall - 1;
return SafeToReclaimEpoch;
}
/// <summary>
/// Reserve entry for thread. This method relies on the fact that no
/// thread will ever have ID 0.
/// </summary>
/// <param name="startIndex">Start index</param>
/// <param name="threadId">Thread id</param>
/// <returns>Reserved entry</returns>
private static int ReserveEntry(int startIndex, int threadId)
{
int current_iteration = 0;
for (; ; )
{
// Reserve an entry in the table.
for (int i = 0; i < kTableSize; ++i)
{
int index_to_test = 1 + ((startIndex + i) & (kTableSize - 1));
if (0 == (threadIndexAligned + index_to_test)->threadId)
{
bool success =
(0 == Interlocked.CompareExchange(
ref (threadIndexAligned+index_to_test)->threadId,
threadId, 0));
if (success)
{
return (int)index_to_test;
}
}
++current_iteration;
}
if (current_iteration > (kTableSize * 10))
{
throw new Exception("Unable to reserve an epoch entry, try increasing the epoch table size (kTableSize)");
}
}
}
/// <summary>
/// Allocate a new entry in epoch table. This is called
/// once for a thread.
/// </summary>
/// <returns>Reserved entry</returns>
private static int ReserveEntryForThread()
{
if (threadId == 0) // run once per thread for performance
{
// For portability(run on non-windows platform)
threadId = Environment.OSVersion.Platform == PlatformID.Win32NT ? (int)Native32.GetCurrentThreadId() : Thread.CurrentThread.ManagedThreadId;
}
int startIndex = Utility.Murmur3(threadId);
return ReserveEntry(startIndex, threadId);
}
/// <summary>
/// Epoch table entry (cache line size).
/// </summary>
[StructLayout(LayoutKind.Explicit, Size = Constants.kCacheLineBytes)]
private struct Entry
{
/// <summary>
/// Thread-local value of epoch
/// </summary>
[FieldOffset(0)]
public int localCurrentEpoch;
/// <summary>
/// ID of thread associated with this entry.
/// </summary>
[FieldOffset(4)]
public int threadId;
[FieldOffset(8)]
public int reentrant;
[FieldOffset(12)]
public fixed int markers[13];
};
private struct EpochActionPair
{
public long epoch;
public Action action;
}
/// <summary>
/// Mechanism for threads to mark some activity as completed until
/// some version by this thread, and check if all active threads
/// have completed the same activity until that version.
/// </summary>
/// <param name="markerIdx">ID of activity</param>
/// <param name="version">Version</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MarkAndCheckIsComplete(int markerIdx, int version)
{
int entry = threadEntryIndex;
if (kInvalidIndex == entry)
{
Debug.WriteLine("New Thread entered during CPR");
Debug.Assert(false);
}
(*(tableAligned + entry)).markers[markerIdx] = version;
// check if all threads have reported complete
for (int index = 1; index <= kTableSize; ++index)
{
int entry_epoch = (*(tableAligned + index)).localCurrentEpoch;
int fc_version = (*(tableAligned + index)).markers[markerIdx];
if (0 != entry_epoch)
{
if (fc_version != version && entry_epoch < int.MaxValue)
{
return false;
}
}
}
return true;
}
}
}

Powered by TurnKey Linux.