mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-20 09:46:20 +00:00
core: New utilities for object lifetime management
This commit is contained in:
parent
989f662f46
commit
352f58b94b
337
base/src/core/include/lifetime.h
Normal file
337
base/src/core/include/lifetime.h
Normal file
@ -0,0 +1,337 @@
|
||||
/*
|
||||
* \brief Utilities for object life-time management
|
||||
* \author Norman Feske
|
||||
* \date 2013-03-09
|
||||
*
|
||||
* This header provides utilities for avoiding dangling pointers. Such a
|
||||
* situation happens when an object disappears while pointers to the object
|
||||
* are still in use. One way to solve this problem is to explicitly notify the
|
||||
* holders of those pointers about the disappearance of the object. But this
|
||||
* would require the object to keep references to those pointer holder, which,
|
||||
* in turn, might disappear as well. Consequently, this approach tends to
|
||||
* become a complex solution, which is prone to deadlocks or race conditions
|
||||
* when multiple threads are involved.
|
||||
*
|
||||
* The utilities provided herein implement a more elegant pattern called
|
||||
* "weak pointers" to deal with such situations. An object that might
|
||||
* disappear at any time is represented by the 'Volatile_object' class
|
||||
* template. It keeps track of a list of so-called weak pointers pointing
|
||||
* to the object. A weak pointer, in turn, holds privately the pointer to the
|
||||
* object alongside a validity flag. It cannot be used to dereference the
|
||||
* object. For accessing the actual object, a locked pointer must be created
|
||||
* from a weak pointer. If this creation succeeds, the object is guaranteed to
|
||||
* be locked (not destructed) until the locked pointer gets destroyed. If the
|
||||
* object no longer exists, the locked pointer will be invalid. This condition
|
||||
* can (and should) be detected via the 'Locked_ptr::is_valid()' function prior
|
||||
* dereferencing the pointer.
|
||||
*
|
||||
* In the event a volatile object gets destructed, all weak pointers that point
|
||||
* to the object are automatically invalidated. So a subsequent conversion into
|
||||
* a locked pointer will yield an invalid pointer, which can be detected (in
|
||||
* contrast to a dangling pointer).
|
||||
*
|
||||
* To use this mechanism, the destruction of a volatile object must be
|
||||
* deferred until no locked pointer points to the object anymore. This is
|
||||
* done by calling the function 'Volatile_object::lock_for_destruction()'
|
||||
* at the beginning of the destructor of the to-be-destructed object.
|
||||
* When this function returns, all weak pointers to the object will have been
|
||||
* invalidated. So it is save to destruct and free the object.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#ifndef _CORE__INCLUDE__LIFETIME_H_
|
||||
#define _CORE__INCLUDE__LIFETIME_H_
|
||||
|
||||
#include <base/lock.h>
|
||||
|
||||
namespace Genode {
|
||||
class Volatile_object_base;
|
||||
class Weak_ptr_base;
|
||||
class Locked_ptr_base;
|
||||
|
||||
template <typename T> struct Volatile_object;
|
||||
template <typename T> struct Weak_ptr;
|
||||
template <typename T> struct Locked_ptr;
|
||||
}
|
||||
|
||||
|
||||
class Genode::Weak_ptr_base : public Genode::List<Weak_ptr_base>::Element
|
||||
{
|
||||
private:
|
||||
|
||||
friend class Volatile_object_base;
|
||||
friend class Locked_ptr_base;
|
||||
|
||||
Lock mutable _lock;
|
||||
Volatile_object_base *_obj;
|
||||
bool _valid; /* true if '_obj' points to an
|
||||
existing object */
|
||||
|
||||
inline void _adopt(Volatile_object_base *obj);
|
||||
inline void _disassociate();
|
||||
|
||||
protected:
|
||||
|
||||
Volatile_object_base *obj() const { return _valid ? _obj: 0; }
|
||||
|
||||
explicit inline Weak_ptr_base(Volatile_object_base *obj);
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Default constructor, produces invalid pointer
|
||||
*/
|
||||
inline Weak_ptr_base();
|
||||
|
||||
inline ~Weak_ptr_base();
|
||||
|
||||
/**
|
||||
* Assignment operator
|
||||
*/
|
||||
inline void operator = (Weak_ptr_base const &other);
|
||||
|
||||
/**
|
||||
* Test for equality
|
||||
*/
|
||||
inline bool operator == (Weak_ptr_base const &other) const;
|
||||
|
||||
/**
|
||||
* Inspection hook for unit test
|
||||
*/
|
||||
void debug_info() const;
|
||||
};
|
||||
|
||||
|
||||
class Genode::Volatile_object_base
|
||||
{
|
||||
private:
|
||||
|
||||
friend class Weak_ptr_base;
|
||||
friend class Locked_ptr_base;
|
||||
|
||||
/**
|
||||
* List of weak pointers currently pointing to the object
|
||||
*/
|
||||
Lock _list_lock;
|
||||
List<Weak_ptr_base> _list;
|
||||
|
||||
/**
|
||||
* Lock used to defer the destruction of an object derived from
|
||||
* 'Volatile_object_base'
|
||||
*/
|
||||
Lock _destruct_lock;
|
||||
|
||||
protected:
|
||||
|
||||
inline ~Volatile_object_base();
|
||||
|
||||
/**
|
||||
* To be called from 'Volatile_object<T>' only
|
||||
*/
|
||||
template <typename T>
|
||||
Weak_ptr<T> _weak_ptr();
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Function to be called by the destructor of a volatile object to
|
||||
* defer the destruction until no 'Locked_ptr' is held to the object.
|
||||
*/
|
||||
void lock_for_destruction() { _destruct_lock.lock(); }
|
||||
|
||||
/**
|
||||
* Inspection hook for unit test
|
||||
*/
|
||||
void debug_info() const;
|
||||
};
|
||||
|
||||
|
||||
class Genode::Locked_ptr_base
|
||||
{
|
||||
protected:
|
||||
|
||||
Volatile_object_base *curr;
|
||||
|
||||
inline Locked_ptr_base(Weak_ptr_base &weak_ptr);
|
||||
inline ~Locked_ptr_base();
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
struct Genode::Weak_ptr : Genode::Weak_ptr_base
|
||||
{
|
||||
/**
|
||||
* Default constructor creates invalid pointer
|
||||
*/
|
||||
Weak_ptr() { }
|
||||
|
||||
/**
|
||||
* Copy constructor
|
||||
*/
|
||||
Weak_ptr(Weak_ptr<T> const &other) : Weak_ptr_base(other.obj()) { }
|
||||
|
||||
/**
|
||||
* Assignment operator
|
||||
*/
|
||||
inline void operator = (Weak_ptr<T> const &other)
|
||||
{
|
||||
*static_cast<Weak_ptr_base *>(this) = other;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
struct Genode::Volatile_object : Genode::Volatile_object_base
|
||||
{
|
||||
Weak_ptr<T> weak_ptr() { return _weak_ptr<T>(); }
|
||||
};
|
||||
|
||||
|
||||
template <typename T>
|
||||
struct Genode::Locked_ptr : Genode::Locked_ptr_base
|
||||
{
|
||||
Locked_ptr(Weak_ptr<T> &weak_ptr) : Locked_ptr_base(weak_ptr) { }
|
||||
|
||||
T *operator -> () { return static_cast<T *>(curr); }
|
||||
|
||||
bool is_valid() const { return curr != 0; }
|
||||
};
|
||||
|
||||
|
||||
/********************
|
||||
** Implementation **
|
||||
********************/
|
||||
|
||||
void Genode::Weak_ptr_base::_adopt(Genode::Volatile_object_base *obj)
|
||||
{
|
||||
if (!obj)
|
||||
return;
|
||||
|
||||
_obj = obj;
|
||||
_valid = true;
|
||||
|
||||
Lock::Guard guard(_obj->_list_lock);
|
||||
_obj->_list.insert(this);
|
||||
}
|
||||
|
||||
|
||||
void Genode::Weak_ptr_base::_disassociate()
|
||||
{
|
||||
/* defer destruction of object */
|
||||
{
|
||||
Lock::Guard guard(_lock);
|
||||
|
||||
if (!_valid)
|
||||
return;
|
||||
|
||||
_obj->_destruct_lock.lock();
|
||||
}
|
||||
|
||||
/*
|
||||
* Disassociate reference from object
|
||||
*
|
||||
* Because we hold the '_destruct_lock', we are safe to do
|
||||
* the list operation. However, after we have released the
|
||||
* 'Weak_ptr_base::_lock', the object may have invalidated
|
||||
* the reference. So we must check for validity again.
|
||||
*/
|
||||
{
|
||||
Lock::Guard guard(_obj->_list_lock);
|
||||
if (_valid)
|
||||
_obj->_list.remove(this);
|
||||
}
|
||||
|
||||
/* release object */
|
||||
_obj->_destruct_lock.unlock();
|
||||
}
|
||||
|
||||
|
||||
Genode::Weak_ptr_base::Weak_ptr_base(Genode::Volatile_object_base *obj)
|
||||
{
|
||||
_adopt(obj);
|
||||
}
|
||||
|
||||
|
||||
Genode::Weak_ptr_base::Weak_ptr_base() : _obj(0), _valid(false) { }
|
||||
|
||||
|
||||
void Genode::Weak_ptr_base::operator = (Weak_ptr_base const &other)
|
||||
{
|
||||
/* self assignment */
|
||||
if (&other == this)
|
||||
return;
|
||||
|
||||
Volatile_object_base *obj = other.obj();
|
||||
_disassociate();
|
||||
_adopt(obj);
|
||||
}
|
||||
|
||||
|
||||
bool Genode::Weak_ptr_base::operator == (Weak_ptr_base const &other) const
|
||||
{
|
||||
if (&other == this)
|
||||
return true;
|
||||
|
||||
Lock::Guard guard_this(_lock), guard_other(other._lock);
|
||||
|
||||
return (!_valid && !other._valid)
|
||||
|| (_valid && other._valid && _obj == other._obj);
|
||||
}
|
||||
|
||||
|
||||
Genode::Weak_ptr_base::~Weak_ptr_base()
|
||||
{
|
||||
_disassociate();
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
Genode::Weak_ptr<T> Genode::Volatile_object_base::_weak_ptr()
|
||||
{
|
||||
Weak_ptr_base result(this);
|
||||
return *static_cast<Weak_ptr<T> *>(&result);
|
||||
}
|
||||
|
||||
|
||||
Genode::Volatile_object_base::~Volatile_object_base()
|
||||
{
|
||||
{
|
||||
Lock::Guard guard(_list_lock);
|
||||
|
||||
Weak_ptr_base *curr = 0;
|
||||
while ((curr = _list.first())) {
|
||||
|
||||
Lock::Guard guard(curr->_lock);
|
||||
curr->_valid = false;
|
||||
_list.remove(curr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Genode::Locked_ptr_base::Locked_ptr_base(Weak_ptr_base &weak_ptr)
|
||||
: curr(0)
|
||||
{
|
||||
Lock::Guard guard(weak_ptr._lock);
|
||||
|
||||
if (!weak_ptr._valid)
|
||||
return;
|
||||
|
||||
curr = weak_ptr._obj;
|
||||
curr->_destruct_lock.lock();
|
||||
}
|
||||
|
||||
|
||||
Genode::Locked_ptr_base::~Locked_ptr_base()
|
||||
{
|
||||
if (curr)
|
||||
curr->_destruct_lock.unlock();
|
||||
}
|
||||
|
||||
#endif /* _CORE__INCLUDE__LIFETIME_H_ */
|
278
base/src/test/lifetime/main.cc
Normal file
278
base/src/test/lifetime/main.cc
Normal file
@ -0,0 +1,278 @@
|
||||
/*
|
||||
* \brief Test for lifetime-management utilities
|
||||
* \author Norman Feske
|
||||
* \date 2013-03-12
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2013 Genode Labs GmbH
|
||||
*
|
||||
* This file is part of the Genode OS framework, which is distributed
|
||||
* under the terms of the GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
/* Genode includes */
|
||||
#include <base/printf.h>
|
||||
#include <base/env.h>
|
||||
#include <base/thread.h>
|
||||
#include <timer_session/connection.h>
|
||||
|
||||
/* core includes */
|
||||
#include <lifetime.h>
|
||||
|
||||
|
||||
/********************************************************************
|
||||
** Hooks for obtaining internal information of the tested classes **
|
||||
********************************************************************/
|
||||
|
||||
static int weak_ptr_cnt;
|
||||
|
||||
|
||||
void Genode::Volatile_object_base::debug_info() const
|
||||
{
|
||||
/* count number of weak pointers pointing to the object */
|
||||
weak_ptr_cnt = 0;
|
||||
for (Weak_ptr_base *curr = _list.first(); curr; curr = curr->next())
|
||||
weak_ptr_cnt++;
|
||||
}
|
||||
|
||||
|
||||
static int weak_ptr_is_valid;
|
||||
|
||||
|
||||
void Genode::Weak_ptr_base::debug_info() const
|
||||
{
|
||||
weak_ptr_is_valid = _valid;
|
||||
}
|
||||
|
||||
|
||||
struct Fatal_error { };
|
||||
|
||||
|
||||
static void assert_weak_ptr_cnt(Genode::Volatile_object_base const *obj,
|
||||
int expected_cnt)
|
||||
{
|
||||
obj->debug_info();
|
||||
|
||||
if (expected_cnt != weak_ptr_cnt) {
|
||||
PERR("unexpected count, expected %d, got %d",
|
||||
expected_cnt, weak_ptr_cnt);
|
||||
throw Fatal_error();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void assert_weak_ptr_valid(Genode::Weak_ptr_base const &ptr, bool valid)
|
||||
{
|
||||
ptr.debug_info();
|
||||
|
||||
if (weak_ptr_is_valid == valid)
|
||||
return;
|
||||
|
||||
PERR("weak pointer unexpectedly %s", valid ? "valid" : "invalid");
|
||||
throw Fatal_error();
|
||||
}
|
||||
|
||||
|
||||
/********************************************
|
||||
** Test for the tracking of weak pointers **
|
||||
********************************************/
|
||||
|
||||
static bool object_is_constructed;
|
||||
|
||||
|
||||
struct Object : Genode::Volatile_object<Object>
|
||||
{
|
||||
Object() { object_is_constructed = true; }
|
||||
|
||||
~Object()
|
||||
{
|
||||
Volatile_object<Object>::lock_for_destruction();
|
||||
object_is_constructed = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
static void test_weak_pointer_tracking()
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
PLOG("construct invalid weak pointer");
|
||||
{
|
||||
Weak_ptr<Object> ptr;
|
||||
assert_weak_ptr_valid(ptr, false);
|
||||
}
|
||||
|
||||
Object *obj = new (env()->heap()) Object;
|
||||
|
||||
Weak_ptr<Object> ptr_1 = obj->weak_ptr();
|
||||
assert_weak_ptr_valid(ptr_1, true);
|
||||
|
||||
Weak_ptr<Object> ptr_2 = obj->weak_ptr();
|
||||
assert_weak_ptr_valid(ptr_2, true);
|
||||
|
||||
assert_weak_ptr_cnt(obj, 2);
|
||||
|
||||
PLOG("test: assign weak pointer to itself");
|
||||
ptr_2 = ptr_2;
|
||||
assert_weak_ptr_cnt(obj, 2);
|
||||
assert_weak_ptr_valid(ptr_2, true);
|
||||
|
||||
{
|
||||
PLOG("test: assign weak pointer to another");
|
||||
Weak_ptr<Object> ptr_3 = ptr_2;
|
||||
assert_weak_ptr_cnt(obj, 3);
|
||||
|
||||
PLOG("test: destruct weak pointer");
|
||||
/* 'ptr_3' gets destructed when leaving the scope */
|
||||
}
|
||||
assert_weak_ptr_cnt(obj, 2);
|
||||
|
||||
PLOG("destruct object");
|
||||
destroy(env()->heap(), obj);
|
||||
|
||||
/*
|
||||
* The destruction of the object should have invalidated all weak pointers
|
||||
* pointing to the object.
|
||||
*/
|
||||
assert_weak_ptr_valid(ptr_1, false);
|
||||
assert_weak_ptr_valid(ptr_2, false);
|
||||
}
|
||||
|
||||
|
||||
/*******************************************
|
||||
** Test for deferring object destruction **
|
||||
*******************************************/
|
||||
|
||||
struct Destruct_thread : Genode::Thread<4096>
|
||||
{
|
||||
Object *obj;
|
||||
|
||||
void entry()
|
||||
{
|
||||
using namespace Genode;
|
||||
PLOG("thread: going to destroy object");
|
||||
destroy(env()->heap(), obj);
|
||||
PLOG("thread: destruction completed, job done");
|
||||
}
|
||||
|
||||
Destruct_thread(Object *obj) : obj(obj) { }
|
||||
};
|
||||
|
||||
|
||||
static void assert_constructed(bool expect_constructed)
|
||||
{
|
||||
if (object_is_constructed == expect_constructed)
|
||||
return;
|
||||
|
||||
PERR("object unexpectedly %sconstructed",
|
||||
!object_is_constructed ? "not" : "");
|
||||
throw Fatal_error();
|
||||
}
|
||||
|
||||
|
||||
static void test_deferred_destruction()
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
static Timer::Connection timer;
|
||||
|
||||
Object *obj = new (env()->heap()) Object;
|
||||
|
||||
Weak_ptr<Object> ptr = obj->weak_ptr();
|
||||
assert_weak_ptr_cnt(obj, 1);
|
||||
assert_weak_ptr_valid(ptr, true);
|
||||
assert_constructed(true);
|
||||
|
||||
/* create thread that will be used to destruct the object */
|
||||
Destruct_thread destruct_thread(obj);
|
||||
|
||||
{
|
||||
/* acquire possession over the object */
|
||||
Locked_ptr<Object> locked_ptr(ptr);
|
||||
|
||||
/* start destruction using dedicated thread */
|
||||
destruct_thread.start();
|
||||
|
||||
/* yield some time to the other thread */
|
||||
timer.msleep(500);
|
||||
|
||||
/* even after the time period, the object should still be alive */
|
||||
assert_constructed(true);
|
||||
|
||||
/* now, we release the locked pointer, the destruction can begin */
|
||||
}
|
||||
|
||||
/*
|
||||
* Now that the thread is expected to be unblocked, yield some time
|
||||
* to actually do the destruction.
|
||||
*/
|
||||
timer.msleep(100);
|
||||
|
||||
assert_constructed(false);
|
||||
|
||||
destruct_thread.join();
|
||||
}
|
||||
|
||||
|
||||
/*******************************************************
|
||||
** Test the failed aquisition of a destructed object **
|
||||
*******************************************************/
|
||||
|
||||
static void test_acquisition_failure()
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
PLOG("create object and weak pointer");
|
||||
Object *obj = new (env()->heap()) Object;
|
||||
Weak_ptr<Object> ptr = obj->weak_ptr();
|
||||
|
||||
PLOG("try to acquire possession over the object");
|
||||
{
|
||||
Locked_ptr<Object> locked_ptr(ptr);
|
||||
|
||||
if (!locked_ptr.is_valid()) {
|
||||
PERR("locked pointer unexpectedly invalid");
|
||||
throw Fatal_error();
|
||||
}
|
||||
|
||||
/* release lock */
|
||||
}
|
||||
|
||||
PLOG("destroy object");
|
||||
destroy(env()->heap(), obj);
|
||||
|
||||
PLOG("try again, this time we should get an invalid pointer");
|
||||
{
|
||||
Locked_ptr<Object> locked_ptr(ptr);
|
||||
|
||||
if (locked_ptr.is_valid()) {
|
||||
PERR("locked pointer unexpectedly valid");
|
||||
throw Fatal_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/******************
|
||||
** Main program **
|
||||
******************/
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
using namespace Genode;
|
||||
|
||||
printf("--- test-lifetime started ---\n");
|
||||
|
||||
printf("\n-- test tracking of weak pointers --\n");
|
||||
test_weak_pointer_tracking();
|
||||
|
||||
printf("\n-- test deferred destruction --\n");
|
||||
test_deferred_destruction();
|
||||
|
||||
printf("\n-- test acquisition failure --\n");
|
||||
test_acquisition_failure();
|
||||
|
||||
printf("\n--- finished test-lifetime ---\n");
|
||||
return 0;
|
||||
}
|
4
base/src/test/lifetime/target.mk
Normal file
4
base/src/test/lifetime/target.mk
Normal file
@ -0,0 +1,4 @@
|
||||
TARGET = test-lifetime
|
||||
SRC_CC = main.cc
|
||||
LIBS = base
|
||||
INC_DIR += $(REP_DIR)/src/core/include
|
36
os/run/lifetime.run
Normal file
36
os/run/lifetime.run
Normal file
@ -0,0 +1,36 @@
|
||||
build "core init drivers/timer test/lifetime"
|
||||
|
||||
create_boot_directory
|
||||
|
||||
install_config {
|
||||
<config>
|
||||
<parent-provides>
|
||||
<service name="ROM"/>
|
||||
<service name="LOG"/>
|
||||
<service name="CAP"/>
|
||||
<service name="CPU"/>
|
||||
<service name="RAM"/>
|
||||
<service name="RM"/>
|
||||
<service name="PD"/>
|
||||
<service name="SIGNAL"/>
|
||||
</parent-provides>
|
||||
<default-route>
|
||||
<any-service> <any-child/> <parent/> </any-service>
|
||||
</default-route>
|
||||
<start name="timer">
|
||||
<resource name="RAM" quantum="1M"/>
|
||||
<provides><service name="Timer"/></provides>
|
||||
</start>
|
||||
<start name="test-lifetime">
|
||||
<resource name="RAM" quantum="10M"/>
|
||||
</start>
|
||||
</config>
|
||||
}
|
||||
|
||||
build_boot_image "core init timer test-lifetime"
|
||||
|
||||
append qemu_args "-nographic -m 64"
|
||||
|
||||
run_genode_until "--- finished test-lifetime ---.*\n" 30
|
||||
|
||||
puts "Test succeeded"
|
Loading…
x
Reference in New Issue
Block a user