Add a timer to the PAL

This adds a way to periodically pool the PAL to see if any timers have
expired.  Timers can be used to periodically provide callbacks to the
rest of snmalloc.
This commit is contained in:
Matthew Parkinson
2021-10-20 09:31:45 +01:00
committed by Matthew Parkinson
parent c1062e629e
commit 20a114cb62
13 changed files with 343 additions and 72 deletions

View File

@@ -3,7 +3,7 @@
#ifdef __cpp_concepts
# include "../ds/concept.h"
# include "pal_consts.h"
# include "pal_ds.h"
# include <utility>
namespace snmalloc

View File

@@ -3,6 +3,7 @@
#include "../ds/defines.h"
#include <atomic>
#include <functional>
namespace snmalloc
{
@@ -85,70 +86,7 @@ namespace snmalloc
/**
* Default Tag ID for the Apple class
*/
static const uint8_t PALAnonDefaultID = 241;
/**
* This struct is used to represent callbacks for notification from the
* platform. It contains a next pointer as client is responsible for
* allocation as we cannot assume an allocator at this point.
*/
struct PalNotificationObject
{
std::atomic<PalNotificationObject*> pal_next = nullptr;
void (*pal_notify)(PalNotificationObject* self);
PalNotificationObject(void (*pal_notify)(PalNotificationObject* self))
: pal_notify(pal_notify)
{}
};
/***
* Wrapper for managing notifications for PAL events
*/
class PalNotifier
{
/**
* List of callbacks to notify
*/
std::atomic<PalNotificationObject*> callbacks{nullptr};
public:
/**
* Register a callback object to be notified
*
* The object should never be deallocated by the client after calling
* this.
*/
void register_notification(PalNotificationObject* callback)
{
callback->pal_next = nullptr;
auto prev = &callbacks;
auto curr = prev->load();
do
{
while (curr != nullptr)
{
prev = &(curr->pal_next);
curr = prev->load();
}
} while (!prev->compare_exchange_weak(curr, callback));
}
/**
* Calls the pal_notify of all the registered objects.
*/
void notify_all()
{
PalNotificationObject* curr = callbacks;
while (curr != nullptr)
{
curr->pal_notify(curr);
curr = curr->pal_next;
}
}
};
static const int PALAnonDefaultID = 241;
/**
* Query whether the PAL supports a specific feature.

163
src/pal/pal_ds.h Normal file
View File

@@ -0,0 +1,163 @@
#pragma once
#include "../ds/defines.h"
#include "../ds/helpers.h"
#include <atomic>
#include <functional>
namespace snmalloc
{
template<typename T>
class PalList
{
/**
* List of callbacks to notify
*/
std::atomic<T*> elements{nullptr};
static_assert(
std::is_same<decltype(T::pal_next), std::atomic<T*>>::value,
"Required pal_next type.");
public:
/**
* Add an element to the list
*/
void add(T* element)
{
auto prev = &elements;
auto curr = prev->load();
do
{
while (curr != nullptr)
{
prev = &(curr->pal_next);
curr = prev->load();
}
} while (!prev->compare_exchange_weak(curr, element));
}
/**
* Applies function to all the elements of the list
*/
void apply_all(function_ref<void(T*)> func)
{
T* curr = elements;
while (curr != nullptr)
{
func(curr);
curr = curr->pal_next;
}
}
};
/**
* This struct is used to represent callbacks for notification from the
* platform. It contains a next pointer as client is responsible for
* allocation as we cannot assume an allocator at this point.
*/
struct PalNotificationObject
{
std::atomic<PalNotificationObject*> pal_next = nullptr;
void (*pal_notify)(PalNotificationObject* self);
PalNotificationObject(void (*pal_notify)(PalNotificationObject* self))
: pal_notify(pal_notify)
{}
};
/***
* Wrapper for managing notifications for PAL events
*/
class PalNotifier
{
/**
* List of callbacks to notify
*/
PalList<PalNotificationObject> callbacks;
public:
/**
* Register a callback object to be notified
*
* The object should never be deallocated by the client after calling
* this.
*/
void register_notification(PalNotificationObject* callback)
{
callbacks.add(callback);
}
/**
* Calls the pal_notify of all the registered objects.
*/
void notify_all()
{
callbacks.apply_all([](auto curr) { curr->pal_notify(curr); });
}
};
class PalTimerObject
{
friend class PalTimer;
template<typename T>
friend class PalList;
std::atomic<PalTimerObject*> pal_next;
void (*pal_notify)(PalTimerObject* self);
uint64_t last_run = 0;
uint64_t repeat;
public:
PalTimerObject(void (*pal_notify)(PalTimerObject* self), uint64_t repeat)
: pal_notify(pal_notify), repeat(repeat)
{}
};
/**
* Simple mechanism for handling timers.
*
* Note: This is really designed for a very small number of timers,
* and this design should be changed if that is no longer the case.
*/
class PalTimer
{
/**
* List of callbacks to notify
*/
PalList<PalTimerObject> timers;
public:
/**
* Register a callback to be called every repeat milliseconds.
*/
void register_timer(PalTimerObject* timer)
{
timers.add(timer);
}
void check(uint64_t time_ms)
{
static std::atomic_flag lock = ATOMIC_FLAG_INIT;
// Depulicate calls into here, and make single threaded.
if (lock.test_and_set())
{
timers.apply_all([time_ms](PalTimerObject* curr) {
if (
(curr->last_run == 0) ||
((time_ms - curr->last_run) > curr->repeat))
{
curr->last_run = time_ms;
curr->pal_notify(curr);
}
});
lock.clear();
}
}
};
} // namespace snmalloc

View File

@@ -6,6 +6,7 @@
#include "../aal/aal.h"
#include "pal_concept.h"
#include "pal_consts.h"
#include "pal_timer_default.h"
#include <cstring>
@@ -27,7 +28,7 @@ namespace snmalloc
* ever use.
*/
template<SNMALLOC_CONCEPT(PALNoAllocBase) BasePAL>
struct PALNoAlloc
struct PALNoAlloc : public PalTimerDefaultImpl<BasePAL>
{
/**
* Bitmap of PalFeatures flags indicating the optional features that this

View File

@@ -1,13 +1,14 @@
#pragma once
#include "../ds/bits.h"
#include "pal_timer_default.h"
namespace snmalloc
{
// Can be extended
// Will require a reserve method in subclasses.
template<class State>
class PALPlainMixin : public State
class PALPlainMixin : public State, public PalTimerDefaultImpl<State>
{
public:
// Notify platform that we will not be using these pages

View File

@@ -4,6 +4,7 @@
# include <iostream>
#endif
#include "../ds/address.h"
#include "pal_timer_default.h"
#if defined(SNMALLOC_BACKTRACE_HEADER)
# include SNMALLOC_BACKTRACE_HEADER
#endif
@@ -39,7 +40,7 @@ namespace snmalloc
* version.
*/
template<class OS>
class PALPOSIX
class PALPOSIX : public PalTimerDefaultImpl<PALPOSIX<OS>>
{
/**
* Helper class to access the `default_mmap_flags` field of `OS` if one
@@ -348,5 +349,19 @@ namespace snmalloc
}
error("Entropy requested on platform that does not provide entropy");
}
static uint64_t internal_time_in_ms()
{
auto hold = KeepErrno();
struct timespec ts;
if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
{
error("Failed to get time");
}
return (static_cast<uint64_t>(ts.tv_sec) * 1000) +
(static_cast<uint64_t>(ts.tv_nsec) / 1000000);
}
};
} // namespace snmalloc

View File

@@ -0,0 +1,32 @@
#pragma once
#include "pal_consts.h"
#include "pal_ds.h"
#include <chrono>
namespace snmalloc
{
template<typename PalTime>
class PalTimerDefaultImpl
{
inline static PalTimer timers{};
public:
static uint64_t time_in_ms()
{
auto time = PalTime::internal_time_in_ms();
// Process timers
timers.check(time);
return time;
}
static void register_timer(PalTimerObject* timer)
{
timers.register_timer(timer);
}
};
}

View File

@@ -2,6 +2,7 @@
#include "../ds/address.h"
#include "../ds/bits.h"
#include "pal_timer_default.h"
#ifdef _WIN32
# ifndef _MSC_VER
@@ -22,9 +23,11 @@
# endif
# endif
# include <chrono>
namespace snmalloc
{
class PALWindows
class PALWindows : public PalTimerDefaultImpl<PALWindows>
{
/**
* A flag indicating that we have tried to register for low-memory
@@ -206,6 +209,14 @@ namespace snmalloc
error("Failed to get entropy.");
return result;
}
static uint64_t internal_time_in_ms()
{
return static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count());
}
};
}
#endif