From 352f58b94bc34967b09882316f1f2def07174f59 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Mon, 11 Mar 2013 11:40:29 +0100 Subject: [PATCH] core: New utilities for object lifetime management --- base/src/core/include/lifetime.h | 337 +++++++++++++++++++++++++++++++ base/src/test/lifetime/main.cc | 278 +++++++++++++++++++++++++ base/src/test/lifetime/target.mk | 4 + os/run/lifetime.run | 36 ++++ 4 files changed, 655 insertions(+) create mode 100644 base/src/core/include/lifetime.h create mode 100644 base/src/test/lifetime/main.cc create mode 100644 base/src/test/lifetime/target.mk create mode 100644 os/run/lifetime.run diff --git a/base/src/core/include/lifetime.h b/base/src/core/include/lifetime.h new file mode 100644 index 000000000..2df404db6 --- /dev/null +++ b/base/src/core/include/lifetime.h @@ -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 + +namespace Genode { + class Volatile_object_base; + class Weak_ptr_base; + class Locked_ptr_base; + + template struct Volatile_object; + template struct Weak_ptr; + template struct Locked_ptr; +} + + +class Genode::Weak_ptr_base : public Genode::List::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 _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' only + */ + template + Weak_ptr _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 +struct Genode::Weak_ptr : Genode::Weak_ptr_base +{ + /** + * Default constructor creates invalid pointer + */ + Weak_ptr() { } + + /** + * Copy constructor + */ + Weak_ptr(Weak_ptr const &other) : Weak_ptr_base(other.obj()) { } + + /** + * Assignment operator + */ + inline void operator = (Weak_ptr const &other) + { + *static_cast(this) = other; + } +}; + + +template +struct Genode::Volatile_object : Genode::Volatile_object_base +{ + Weak_ptr weak_ptr() { return _weak_ptr(); } +}; + + +template +struct Genode::Locked_ptr : Genode::Locked_ptr_base +{ + Locked_ptr(Weak_ptr &weak_ptr) : Locked_ptr_base(weak_ptr) { } + + T *operator -> () { return static_cast(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 +Genode::Weak_ptr Genode::Volatile_object_base::_weak_ptr() +{ + Weak_ptr_base result(this); + return *static_cast *>(&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_ */ diff --git a/base/src/test/lifetime/main.cc b/base/src/test/lifetime/main.cc new file mode 100644 index 000000000..31d3293ca --- /dev/null +++ b/base/src/test/lifetime/main.cc @@ -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 +#include +#include +#include + +/* core includes */ +#include + + +/******************************************************************** + ** 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_is_constructed = true; } + + ~Object() + { + Volatile_object::lock_for_destruction(); + object_is_constructed = false; + } +}; + + +static void test_weak_pointer_tracking() +{ + using namespace Genode; + + PLOG("construct invalid weak pointer"); + { + Weak_ptr ptr; + assert_weak_ptr_valid(ptr, false); + } + + Object *obj = new (env()->heap()) Object; + + Weak_ptr ptr_1 = obj->weak_ptr(); + assert_weak_ptr_valid(ptr_1, true); + + Weak_ptr 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 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 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 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 ptr = obj->weak_ptr(); + + PLOG("try to acquire possession over the object"); + { + Locked_ptr 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 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; +} diff --git a/base/src/test/lifetime/target.mk b/base/src/test/lifetime/target.mk new file mode 100644 index 000000000..e68fc9379 --- /dev/null +++ b/base/src/test/lifetime/target.mk @@ -0,0 +1,4 @@ +TARGET = test-lifetime +SRC_CC = main.cc +LIBS = base +INC_DIR += $(REP_DIR)/src/core/include diff --git a/os/run/lifetime.run b/os/run/lifetime.run new file mode 100644 index 000000000..36f3a10a0 --- /dev/null +++ b/os/run/lifetime.run @@ -0,0 +1,36 @@ +build "core init drivers/timer test/lifetime" + +create_boot_directory + +install_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"