[cpp-sp] branch main updated: Flesh out dummy in-memory cache impl.
Scott Cantor
cantor.2 at osu.edu
Tue Sep 16 17:28:42 UTC 2025
This is an automated email from the git hooks/post-receive script.
scantor pushed a commit to branch main
in repository cpp-sp.
View the commit online:
http://git.shibboleth.net/view/?p=cpp-sp.git;a=commit;h=1d38cd953a0a59c7b88674a5af5a242209ea55c0
The following commit(s) were added to refs/heads/main by this push:
new 1d38cd95 Flesh out dummy in-memory cache impl.
1d38cd95 is described below
commit 1d38cd953a0a59c7b88674a5af5a242209ea55c0
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Tue Sep 16 13:28:38 2025 -0400
Flesh out dummy in-memory cache impl.
---
shibsp/session/SessionCacheSPI.h | 3 +-
shibsp/session/impl/MemorySessionCache.cpp | 258 ++++++++++++++++++++++++++++-
2 files changed, 253 insertions(+), 8 deletions(-)
diff --git a/shibsp/session/SessionCacheSPI.h b/shibsp/session/SessionCacheSPI.h
index 15f64a3e..407690db 100644
--- a/shibsp/session/SessionCacheSPI.h
+++ b/shibsp/session/SessionCacheSPI.h
@@ -105,7 +105,8 @@ namespace shibsp {
*
* <p>The specified version MUST be the current version or the update should be aborted.</p>
*
- * <p>The caller retains ownership of the session data object.</p>
+ * <p>The caller retains ownership of the session data object, though it will be modified to
+ * reflect the updated version.</p>
*
* <p>The return value signals success or a version mismatch/collision.</p>
*
diff --git a/shibsp/session/impl/MemorySessionCache.cpp b/shibsp/session/impl/MemorySessionCache.cpp
index 017f92a9..8828df5f 100644
--- a/shibsp/session/impl/MemorySessionCache.cpp
+++ b/shibsp/session/impl/MemorySessionCache.cpp
@@ -27,10 +27,20 @@
#include "csprng/csprng.hpp"
#include "session/AbstractSessionCache.h"
#include "logging/Category.h"
+#include "util/Date.h"
#include "util/Misc.h"
#include <boost/property_tree/ptree.hpp>
+#ifndef WIN32
+# include <signal.h>
+# ifdef HAVE_PTHREAD
+# include <pthread.h>
+# else
+# error "This implementation is for POSIX platforms."
+# endif
+#endif
+
using namespace shibsp;
using namespace boost::property_tree;
using namespace std;
@@ -41,6 +51,9 @@ namespace {
MemorySessionCache(const ptree& pt);
~MemorySessionCache();
+ bool start();
+ void stop();
+
string cache_create(SPRequest* request, DDF& sessionData);
DDF cache_read(
SPRequest* request,
@@ -56,33 +69,89 @@ namespace {
void cache_remove(SPRequest* request, const char* key);
private:
+ static void* memory_cleanup_fn(void*);
+
+ Category& m_spilog;
duthomhas::csprng m_rng;
+ mutex m_lock;
+ map<string,pair<DDF,time_t>> m_storage;
+
+ time_t m_cleanupInterval;
+ condition_variable m_mem_cleanup_wait;
+ thread m_mem_cleanup_thread;
};
};
+static const char MEMORY_CLEANUP_INTERVAL_PROP_NAME[] = "memoryCleanupInterval";
+static unsigned int MEMORY_CLEANUP_INTERVAL_PROP_DEFAULT = 1800;
+
namespace shibsp {
SessionCache* SHIBSP_DLLLOCAL MemorySessionCacheFactory(ptree& pt, bool deprecationSupport) {
return new MemorySessionCache(pt);
}
}
-MemorySessionCache::MemorySessionCache(const ptree& pt) : AbstractSessionCache(pt)
+MemorySessionCache::MemorySessionCache(const ptree& pt)
+ : AbstractSessionCache(pt), m_spilog(Category::getInstance(SHIBSP_LOGCAT ".SessionCache.Memory"))
{
+ m_cleanupInterval = getUnsignedInt(MEMORY_CLEANUP_INTERVAL_PROP_NAME, MEMORY_CLEANUP_INTERVAL_PROP_DEFAULT);
+ if (!m_cleanupInterval) {
+ m_spilog.info("%s was zero, disabling memmory back-end cleanup thread", MEMORY_CLEANUP_INTERVAL_PROP_NAME);
+ }
}
MemorySessionCache::~MemorySessionCache()
{
}
-string MemorySessionCache::cache_create(SPRequest* request, DDF& sessionData)
+bool MemorySessionCache::start()
{
- return hex_encode(m_rng(string(16,0)));
+ if (!AbstractSessionCache::start()) {
+ return false;
+ }
+
+#ifdef HAVE_CXX17
+ if (m_cleanupInterval) {
+ try {
+ m_mem_cleanup_thread = thread(memory_cleanup_fn, this);
+ return true;
+ }
+ catch (const system_error& e) {
+ m_spilog.error("error starting cleanup thread: %s", e.what());
+ }
+ return false;
+ }
+#endif
+ return true;
}
-bool MemorySessionCache::cache_update(SPRequest* request, const char* key, unsigned int version, DDF& data)
+void MemorySessionCache::stop()
{
- // We can't update a session because there's no cross-process copy of it.
- return false;
+ AbstractSessionCache::stop();
+#ifdef HAVE_CXX17
+ if (m_cleanupInterval) {
+ m_mem_cleanup_wait.notify_all();
+ if (m_mem_cleanup_thread.joinable()) {
+ m_mem_cleanup_thread.join();
+ }
+ }
+#endif
+}
+string MemorySessionCache::cache_create(SPRequest* request, DDF& sessionData)
+{
+ lock_guard<mutex> locker(m_lock);
+
+ int attempts = 0;
+ do {
+ string key = hex_encode(m_rng(string(16,0)));
+ if (m_storage.find(key) == m_storage.end()) {
+ m_storage[key] = make_pair(sessionData.copy(), time(nullptr));
+ return key;
+ }
+ } while (++attempts < 3);
+
+ m_spilog.error("failed to write new session after 3 attempts to generate a unique key");
+ throw IOException("Exhausted attempts to generate a unique session key.");
}
DDF MemorySessionCache::cache_read(
@@ -95,14 +164,189 @@ DDF MemorySessionCache::cache_read(
const char* client_addr
)
{
- return DDF();
+ lock_guard<mutex> locker(m_lock);
+
+ auto entry = m_storage.find(key);
+ if (entry == m_storage.end()) {
+ m_lock.unlock();
+ return DDF();
+ }
+
+ time_t now = time(nullptr);
+
+ if (timeout ) {
+ if (entry->second.second + timeout < now) {
+ if (m_spilog.isInfoEnabled()) {
+ string ts(date::format("%FT%TZ", chrono::system_clock::from_time_t(entry->second.second)));
+ m_spilog.info("session (%s) expired for inactivity, timeout (%lu), last access (%s)", key, timeout, ts.c_str());
+ }
+ m_lock.unlock();
+ cache_remove(request, key);
+ return DDF();
+ }
+ }
+
+ const char* appId = entry->second.first["app_id"].string();
+ if (strcmp(applicationId, appId)) {
+ m_spilog.warn("session (%s) issued for application (%s), accessed via application (%s)", key, appId, applicationId);
+ return DDF();
+ }
+
+ if (lifetime) {
+ time_t start = entry->second.first["ts"].longinteger();
+ if (start + lifetime < now) {
+ if (m_spilog.isInfoEnabled()) {
+ string created(date::format("%FT%TZ", chrono::system_clock::from_time_t(start)));
+ string expired(date::format("%FT%TZ", chrono::system_clock::from_time_t(start + lifetime)));
+ m_spilog.info("session (%s) has expired, created (%s), expired (%s)", key, created.c_str(), expired.c_str());
+ }
+ m_lock.unlock();
+ cache_remove(request, key);
+ return DDF();
+ }
+ }
+
+ // TODO: Implement the fuzzy address matching.
+ if (client_addr) {
+ const char* family = getAddressFamily(client_addr);
+ const char* addr = entry->second.first[family].string();
+ if (addr) {
+ if (strcmp(client_addr, addr)) {
+ m_spilog.info("session (%s) invalid, bound to address (%s), accessed from (%s)", key, addr, client_addr);
+ m_lock.unlock();
+ return DDF();
+ }
+ }
+ else {
+ // We have to rebind the session to a new address family, requiring an update to the session.
+ m_spilog.info("attempting update of session (%s) to rebind to new address (%s)", key, client_addr);
+
+ // Fill in the new address and attempt the update.
+ entry->second.first.addmember(family).string(client_addr);
+ unsigned int oldver = entry->second.first.getmember("ver").integer();
+ entry->second.first.getmember("ver").integer(oldver == 0 ? 2 : oldver + 1);
+ }
+ }
+
+ entry->second.second = now;
+ return entry->second.first.copy();
}
bool MemorySessionCache::cache_touch(SPRequest* request, const char* key, unsigned int version, unsigned int timeout)
{
+ lock_guard<mutex> locker(m_lock);
+
+ auto entry = m_storage.find(key);
+ if (entry != m_storage.end()) {
+
+ time_t now = time(nullptr);
+
+ if (timeout && entry->second.second + timeout < now) {
+ if (m_spilog.isInfoEnabled()) {
+ string ts(date::format("%FT%TZ", chrono::system_clock::from_time_t(entry->second.second)));
+ m_spilog.info("session (%s) expired for inactivity, timeout (%lu), last access (%s)", key, timeout, ts.c_str());
+ }
+ m_storage.erase(entry);
+ return false;
+ }
+
+ entry->second.second = now;
+ return true;
+ }
+
+ return false;
+}
+
+bool MemorySessionCache::cache_update(SPRequest* request, const char* key, unsigned int version, DDF& data)
+{
+ lock_guard<mutex> locker(m_lock);
+
+ auto entry = m_storage.find(key);
+ if (entry == m_storage.end()) {
+ return false;
+ }
+
+ unsigned int oldver = entry->second.first.getmember("ver").integer();
+ if (oldver == 0) {
+ oldver = 1;
+ }
+
+ if (version != oldver) {
+ return false;
+ }
+
+ data.addmember("ver").integer(++version);
+ entry->second.first.destroy();
+ entry->second.first = data.copy();
+ entry->second.second = time(nullptr);
return true;
}
void MemorySessionCache::cache_remove(SPRequest* request, const char* key)
{
+ lock_guard<mutex> locker(m_lock);
+ m_storage.erase(key);
+}
+
+void* MemorySessionCache::memory_cleanup_fn(void* p)
+{
+ MemorySessionCache* pcache = reinterpret_cast<MemorySessionCache*>(p);
+
+ static const char MEMORY_TIMEOUT_PROP_NAME[] = "memoryTimeout";
+ static unsigned int MEMORY_TIMEOUT_PROP_DEFAULT = 3600 * 4;
+
+ unsigned int memoryTimeout = pcache->getUnsignedInt(MEMORY_TIMEOUT_PROP_NAME, MEMORY_TIMEOUT_PROP_DEFAULT);
+ if (memoryTimeout == 0) {
+ memoryTimeout = MEMORY_TIMEOUT_PROP_DEFAULT;
+ }
+
+#ifndef WIN32
+ // Bblock all signals.
+ sigset_t sigmask;
+ sigfillset(&sigmask);
+ pthread_sigmask(SIG_BLOCK, &sigmask, nullptr);
+#endif
+
+ mutex internal_mutex;
+ unique_lock lock(internal_mutex);
+
+ pcache->m_spilog.info("memory back-end cleanup thread started...run every %u secs, timeout after %u secs",
+ pcache->m_cleanupInterval, memoryTimeout);
+
+ while (!pcache->isShutdown()) {
+ pcache->m_mem_cleanup_wait.wait_for(lock, chrono::seconds(pcache->m_cleanupInterval));
+
+ if (pcache->isShutdown()) {
+ pcache->m_spilog.debug("memory back-end cleanup thread shutting down");
+ break;
+ }
+
+ // Ok, let's run through the cleanup process and clean out
+ // really old sessions. We're brute forcing this because this
+ // implementation is really just for testing anyway.
+
+ time_t stale = time(nullptr) - memoryTimeout;
+
+ pcache->m_spilog.debug("memory back-end cleanup thread running");
+
+ vector<string> stale_keys;
+
+ lock_guard<mutex> locker(pcache->m_lock);
+ for (auto& session : pcache->m_storage) {
+ // If the last access was BEFORE the stale timeout...
+ if (session.second.second < stale) {
+ session.second.first.destroy();
+ stale_keys.push_back(session.first);
+ }
+ }
+ for (const auto& key : stale_keys) {
+ pcache->m_storage.erase(key);
+ }
+
+ pcache->m_spilog.debug("memory back-end cleanup thread completed work");
+ }
+
+ pcache->m_spilog.info("memory back-end cleanup thread exiting");
+
+ return nullptr;
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list