From 1e22817dfb175e3974fcf93a3d755e2346ec3265 Mon Sep 17 00:00:00 2001 From: Niels Sascha Reedijk Date: Tue, 30 Aug 2022 06:52:56 +0100 Subject: [PATCH] NetServices: add the BExclusiveBorrow smart pointer This smart pointer is designed to help with putting some explicitness and safety around the case where someone will use their own object that implements the BDataIO interface to store the body of a network request. By default, BDataIO objects do not require or enforce thread safety. Since accessing these unsynchronized objects between two threads is undefined behavior, it should be explicitly discouraged. The BExclusiveBorrow/BBorrow smart pointer helper helps solve that by enforcing the limitations on using an unsynchronized object in two threads. When used correctly, there is a runtime check on incorrect use by the developer. This should help write better code. The design is based on shared_ptr, including having an admin block akin the control block, that manages the internal object. This type-erased admin block has the advantage that it allows the owner to have a different type than the borrower. It also handles cases where the lifetime of the borrower is longer than the owner: the borrower can continue to use the object until they want to return it, after which it will be cleaned up. This will make it possible to do some fire and forget pattern in the network services kit, where someone may just wants to create a file and borrow it to the network request, and care about further processing the file in the future. Change-Id: Ie9b7e7472c868b60f663b4db4fa449d421e447eb --- docs/user/netservices/ExclusiveBorrow.dox | 565 ++++++++++++++++++ .../private/netservices2/ExclusiveBorrow.h | 345 +++++++++++ .../net/netservices2/ExclusiveBorrowTest.cpp | 218 +++++++ .../net/netservices2/ExclusiveBorrowTest.h | 26 + src/tests/kits/net/netservices2/Jamfile | 1 + .../net/netservices2/ServicesKitTestAddon.cpp | 2 + 6 files changed, 1157 insertions(+) create mode 100644 docs/user/netservices/ExclusiveBorrow.dox create mode 100644 headers/private/netservices2/ExclusiveBorrow.h create mode 100644 src/tests/kits/net/netservices2/ExclusiveBorrowTest.cpp create mode 100644 src/tests/kits/net/netservices2/ExclusiveBorrowTest.h diff --git a/docs/user/netservices/ExclusiveBorrow.dox b/docs/user/netservices/ExclusiveBorrow.dox new file mode 100644 index 0000000000..5fa358080d --- /dev/null +++ b/docs/user/netservices/ExclusiveBorrow.dox @@ -0,0 +1,565 @@ +/* + * Copyright 2021 Haiku, Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Niels Sascha Reedijk, niels.reedijk@gmail.com + * + * Corresponds to: + * headers/private/netservices2/ExclusiveBorrow.h hrev????? + */ + + +#if __cplusplus >= 201703L + + +/*! + \file ExclusiveBorrow.h + \ingroup netservices + \brief Provides the BExclusiveBorrow smart pointer. +*/ + +namespace BPrivate { + +namespace Network { + + +/*! + \class BBorrowError + \ingroup netservices + \brief Error while handling a BExclusiveBorrow or BBorrow object. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrowError::BBorrowError(const char* origin) + \brief Constructor that sets the \a origin. + + \since Haiku R1 +*/ + + +/*! + \fn virtual const char* BBorrowError::Message() const noexcept override + \brief Get a pointer to the message describing the borrow error. + + \since Haiku R1 +*/ + + +//! \cond INTERNAL + + +/*! + \class BorrowAdmin + \brief Internal class that provides admin block for BExclusiveBorrow and BBorrow. + + This base class provides the admin functions to deal with the underlying objects that are + managed by this smart pointers. It is implemented in such a way that it provides type erasure + for the actual smart objects, to enable them to allow for a BExclusiveBorrow to have a + BBorrow higher up in the class inheritance. + + \since Haiku R1 +*/ + + +/*! + \fn BorrowAdmin::~BorrowAdmin() + \brief Destructor + + \since Haiku R1 +*/ + + +/*! + \fn virtual void BorrowAdmin::Cleanup() noexcept + \brief Hook function that triggers the cleanup of the underlying object. + + Implemented by BorrowPointer + + \since Haiku R1 +*/ + + +/*! + \fn virtual void BorrowAdmin::ReleasePointer() noexcept + \brief Hook function to relinquish ownership of the underlying pointer. + + When BExclusiveBorrow::Release() is called, ownership of the pointer is transferred to the + caller. This hook function allows the BorrowPointer to release the object so that there will + not be a double delete when the BExclusiveBorrow is cleaned up. + + \since Haiku R1 +*/ + + +/*! + \fn BorrowAdmin::BorrowAdmin() + \brief Constructor + + \since Haiku R1 +*/ + + +/*! + \fn void BorrowAdmin::Borrow() + \brief Register that a BBorrow object is created + + \exception BBorrowError In case the object already is borrowed. + + \since Haiku R1 +*/ + + +/*! + \fn void BorrowAdmin::Return() noexcept + \brief Register that the BBorrow object no longer borrows the object. + + This also cleans up the internal object if the corresponding BExclusiveBorrow no longer + exists. + + \since Haiku R1 +*/ + + +/*! + \fn void BorrowAdmin::Forfeit() noexcept + \brief Register that the BExclusiveBorrow object no longer wants to own the object. + + This also cleans up the internal object if there is no BBorrow object. + + \since Haiku R1 +*/ + + +/*! + \fn bool BorrowAdmin::IsBorrowed() noexcept + \brief Check if the current object is borrowed. + + \since Haiku R1 +*/ + + +/*! + \fn void BorrowAdmin::Release() + \brief Cleanup the BorrowAdmin object without cleaning up the underlying object. + + This method is called by BExclusiveBorrow::Release(), where the smart pointer is emptied and + the ownership of the object is transferred to the caller. + + \exception BBorrowError The ownership cannot be released if the object is borrowed. + + \since Haiku R1 +*/ + + +/*! + \class BorrowPointer + \ingroup netservices + \brief Type derived from BorrowAdmin, which administrates the type-specific pointer. + + In order to accomplish type erasure in BExclusiveBorrow and BBorrow, this derived type handles + the actual owned pointer. + + \tparam T The type of the object that the BExclusiveBorrow instance owns. + + \since Haiku R1 +*/ + + +/*! + \fn BorrowPointer::BorrowPointer(T* object) noexcept + \brief Construct a new admin block to manage the \a object. + + \since Haiku R1 +*/ + + +/*! + \fn BorrowPointer::~BorrowPointer() + \brief Destructor that deletes the owned object. + + \since Haiku R1 +*/ + + +/*! + \fn virtual void BorrowPointer::Cleanup() noexcept override + \brief Implementation of the Cleanup() hook function to delete the admin block and free + resources. + + \since Haiku R1 +*/ + + +/*! + \fn virtual void BorrowPointer::ReleasePointer() noexcept override + \brief Implementation of the ReleasePointer() hook function to make sure that the pointer + will not be freed when the object is destroyed. + + \since Haiku R1 +*/ + + +//! \endcond + + +/*! + \class BExclusiveBorrow + \ingroup netservices + \brief Smart pointer that allows shared ownership of an object with exclusive access. + + This smart pointer was designed to support the particular pattern where a non-threadsafe or + non-lockable needs to be shared between two threads, where only one can have access to the + underlying object at the time. + + When creating a new object, the underlying object can be accessed using the dereference + operator overloads as if with any other smart pointer. This ownership can then be borrowed + by creating a \ref BBorrow object. At that stage, the original owner can no longer access the + underlying object, until the borrow is returned. The borrow can access the object as long as + they retain the borrow. The borrow is returned by the borrow object going out of scope, or by + the borrow object being assigned a \c nullptr object. At that stage, the original owner regains + access. + + \code + // Create a newly owned string object. + BExclusiveBorrow owner = make_exclusive_borrow("Initial value"); + + // Access the exclusively accessibe object and set it to a different value + owner->SetTo("New value set by owner"); + + // Create a borrow. + BBorrow borrow = BBorrow(owner); + + try { + owner->SetTo("Another value set by owner"); + } catch (const BorrowError& e) { + // This error will be thrown because the `owner` cannot access the object while borrowed. + } + + try { + BBorrow secondBorrow = BBorrow(owner); + } catch (const BorrowError& e) { + // This error will be thrown because there cannot be more than one borrow at a time. + } + + // The `borrow` has exclusive access to the underlying BString object + borrow->SetTo("A value set by the borrower"); + + // The borrow is returned by explicitly setting it to `nullptr` or by having the object go out + // of scope. + borrow = nullptr; + + // The owner can access the object again + assert(*owner == "A value set by the borrower"); + \endcode + + \par Object Lifetime Management + The BExclusiveBorrow and BBorrow pair manage the lifetime of the underlying object, meaning + the memory will be freed when the object is no longer referenced by either the owner or the + borrower. It is possible to get the ownership of the underlying object through the + \ref BExclusiveBorrow::Release() method. This returns a \c unique_ptr. + + \par Creating New Objects + When creating a BExclusiveBorrow object, you can use the \ref BExclusiveBorrow(T* object) + constructor to create a new smart pointer that takes an \em existing underlying object. Note + that the smart pointer will assume ownership, meaning that you should also have ownership of + that object. If you want to create a BExclusiveBorrow object for a new object, then you can use + the \ref make_exclusive_borrow() function to create a new object. + + \par Move Semantics and Empty Objects + The template class is designed around having an underlying object value, and in most cases will + have an underlying object. However, there may be cases where a BExclusiveOwner or BBorrow + object will not have an internal value. This either happens when it is explicitly assigned an + empty value, or after the object has been moved. You can check whether the object has a value + through the \ref HasValue() method. Trying to access an empty object will throw a + \ref BBorrowError. + + \par Checked Access + The semantics of the exclusive ownership are enforced by this class. The rules are: + - There can only be one owner. The object cannot be copied, only moved. + - There can only be one borrower at a time. The borrow object cannot be copied, only moved. + - If one tries to create an additional borrow, an exception is thrown. + - If an object is borrowed, accessing it through the owner will throw exceptions. + + \par Casting Pointers between Owner and Borrower + For some design patterns, you may want to be able to cast the type of the owner to a related + type for the borrower. For example, the Network Services kit accepts a \c BBorrow type + in order to allow the user to specify where to write the content of a network request to. The + \ref BDataIO itself is an abstract interface to read and write data from an object. A user + will most likely use a \ref BFile or \ref BMallocIO as underlying objects, both of which + have \ref BDataIO as their base class. + + \par + Due to the specialized constructor of \ref BBorrow, it is possible to cast between compatible + pointer types, without loosing the advantages of properly cleaning up the object when the + borrow and the owner go out of scope. In the internals of the template, a type erasure + technique similar to that of \c std::shared_ptr is used. + + \code + // Create a new BFile object, which inherits the BDataIO class. + BExclusiveBorrow owner = make_exclusive_borrow("path/to/file", B_READ_WRITE); + // The following succeeds because a BFile pointer can be assigned to a BDataIO pointer. + BBorrow borrow = BBorrow(owner); + \endcode + + \par Multithread Safety, and Performance Cost + This smart object uses atomics to synchronize the ownership and borrows of the object, and to + enforce all the checks that were mentioned previously. The atomics guarantee that when you + want to access the object in BExclusiveBorrow, that this only succeeds after any outstanding + borrow is completed, otherwise an exception is thrown. While atomics can be used as a method of + synchronization, this templace class is \em not designed for that and it does not have the + tools to help doing that. If you need to synchronize object access between through threads, you + should use semaphores or thread joins instead. + + \tparam T The type of object for this smart pointer. + + \since Haiku R1 +*/ + +/*! + \fn BExclusiveBorrow::BExclusiveBorrow() noexcept + \brief Create a new smart pointer with no value. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow::BExclusiveBorrow(nullptr_t) noexcept + \brief Special constructor that creates a new smart pointer with no value. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow::BExclusiveBorrow(T* object) + \brief Create a new smart pointer that takes ownership of the \a object. + + \param object The object to wrap inside this smart pointer. + + \exception std::bad_alloc In case there are issues allocating memory for the internals of + the smart pointer. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow::~BExclusiveBorrow() + \brief Destructor. + + If the smart pointer is not empty, the underlying object will be deleted if there no longer + is a borrow accessing it. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow::BExclusiveBorrow(BExclusiveBorrow&& other) noexcept + \brief Move constructor. + + \param other The object to move from. It will be left empty after the move. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow& BExclusiveBorrow::operator=(BExclusiveBorrow&& other) noexcept + \brief Move assignment. + + \param other The object to move from. It will be left empty after the move. + + \since Haiku R1 +*/ + + +/*! + \fn bool BExclusiveBorrow::HasValue() const noexcept + \brief Check if the object has a value or is empty. + + \since Haiku R1 +*/ + + +/*! + \fn T& BExclusiveBorrow::operator*() const + \brief Dereferences the pointer. + + \exception BBorrowError This exception is raised if the object is borrowed, or if it is empty. + + \since Haiku R1 +*/ + + +/*! + \fn T* BExclusiveBorrow::operator->() const + \brief Dereferences the pointer. + + \exception BBorrowError This exception is raised if the object is borrowed, or if it is empty. + + \since Haiku R1 +*/ + + +/*! + \fn std::unique_ptr BExclusiveBorrow::Release() + \brief Returns a unique_ptr of the inner object and releases the ownership. + + \exception BBorrowError This exception is raised if the object is borrowed, or if it is empty. + + \since Haiku R1 +*/ + + +/*! + \class BBorrow + \ingroup netservices + \brief Smart pointer that borrows an object from a \ref BExclusiveBorrow owner. + + The BBorrow smart pointer is the accompanyment to the \ref BExclusiveBorrow owner object. See + the documentation on that template class on how to use the smart pointer pairs to express and + enforce exclusive ownership between the owner and the borrower. + + Like a BExclusiveBorrow object, a BBorrow object can either have a borrow or be empty. When it + is empty, it means the current object is not borrowing anything at that moment. Any calls to + access the underlying data will fail in that case. + + \tparam T The type of object that is owned by this smart pointer. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow::BBorrow() noexcept + \brief Create a new smart pointer with no value. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow::BBorrow(nullptr_t) noexcept + \brief Special constructor that builds an empty borrow object. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow::BBorrow(BExclusiveBorrow

& owner) + \brief Construct a borrowed object from the \a owner. + + \param owner The owner to borrow from. + + \exception BBorrowError In case the \a owner already borrowed their object, or in case the + \a owner is an empty object, as you cannot borrow something that is not there. + + \tparam T The type of object for this BBorrow object. + \tparam P The type of objedt for the BExclusiveBorrow object. This allows you to have different + types between the owner and the borrower, with the requirement that a pointer to type P can + be cast to a pointer of type T without issue. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow::BBorrow(BBorrow&& other) noexcept + \brief Move constructor. + + \param other The object to move from. It will be left empty after the move. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow& BBorrow::operator=(BBorrow&& other) noexcept + \brief Move assignment. + + \param other The object to move from. It will be left empty after the move. + + \since Haiku R1 +*/ + + +/*! + \fn BBorrow::~BBorrow() + \brief Destructor that returns the object to the original owner. + + If the original owner no longer exists, the underlying object will be deleted. + + \since Haiku R1 +*/ + + +/*! + \fn bool BBorrow::HasValue() const noexcept + \brief Check if the object has a value or is empty. + + \since Haiku R1 +*/ + + +/*! + \fn T& BBorrow::operator*() const + \brief Dereference operator. + + \exception BBorrowError When the smart pointer is empty and there is no object to access. + + \since Haiku R1 +*/ + + +/*! + \fn T* BBorrow::operator->() const + \brief Dereference operator. + + \exception BBorrowError When the smart pointer is empty and there is no object to access. + + \since Haiku R1 +*/ + + +/*! + \fn void BBorrow::Return() noexcept + \brief Return object to the owner. + + The current object will be set to be an empty object after this call. If the object is already + empty, this call will not do anything. If the owner no longer exists, the object will be + disposed off. + + \since Haiku R1 +*/ + + +/*! + \fn BExclusiveBorrow make_exclusive_borrow(_Args&& ...__args) + \ingroup netservices + \brief Create a new object that is managed by a BExclusiveBorrow smart pointer. + + This is a convenience template function to the likes of \c std::make_unique(). It allows you to + directly create the \ref BExclusiveBorrow smart pointer around a newly allocated object. + + \tparam T The type of the object that will be created. + \tparam _Args Arguments to be passed to the constructor of T. + + \exception std::bad_alloc In case there are issues allocating the new object. + \exception ... Any other exception that is thrown by the constructor of the object T. + + \since Haiku R1 +*/ + + +} // namespace Network + +} // namespace BPrivate + +# endif diff --git a/headers/private/netservices2/ExclusiveBorrow.h b/headers/private/netservices2/ExclusiveBorrow.h new file mode 100644 index 0000000000..04aaa63ddb --- /dev/null +++ b/headers/private/netservices2/ExclusiveBorrow.h @@ -0,0 +1,345 @@ +/* + * Copyright 2022 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + */ + +#ifndef _B_EXCLUSIVE_BORROW_H +#define _B_EXCLUSIVE_BORROW_H + +#include +#include + +#include + +namespace BPrivate { + +namespace Network { + + +class BBorrowError : public BError { +public: + BBorrowError(const char* origin) + : BError(origin) + { + + } + + virtual const char* + Message() const noexcept override + { + return "BBorrowError"; + } +}; + + +class BorrowAdmin { +private: + static constexpr uint8 kOwned = 0x1; + static constexpr uint8 kBorrowed = 0x2; + std::atomic fState = kOwned; + +protected: + virtual ~BorrowAdmin() = default; + + virtual void Cleanup() noexcept {}; + virtual void ReleasePointer() noexcept {}; +public: + BorrowAdmin() noexcept + { + + } + + + void + Borrow() + { + auto alreadyBorrowed = (fState.fetch_or(kBorrowed) & kBorrowed) == kBorrowed; + if (alreadyBorrowed) { + throw BBorrowError(__PRETTY_FUNCTION__); + } + } + + + void + Return() noexcept + { + auto cleanup = (fState.fetch_and(~kBorrowed) & kOwned) != kOwned; + if (cleanup) + this->Cleanup(); + } + + + void + Forfeit() noexcept + { + auto cleanup = (fState.fetch_and(~kOwned) & kBorrowed) != kBorrowed; + if (cleanup) + this->Cleanup(); + } + + + bool + IsBorrowed() noexcept + { + return (fState.load() & kBorrowed) == kBorrowed; + } + + + void + Release() + { + if ((fState.load() & kBorrowed) == kBorrowed) + throw BBorrowError(__PRETTY_FUNCTION__); + this->ReleasePointer(); + this->Cleanup(); + } +}; + + +template +class BorrowPointer : public BorrowAdmin +{ +public: + BorrowPointer(T* object) noexcept + : fPtr(object) + { + + } + + virtual ~BorrowPointer() { + delete fPtr; + } + +protected: + virtual void + Cleanup() noexcept override + { + delete this; + } + + virtual void + ReleasePointer() noexcept override + { + fPtr = nullptr; + } + +private: + T* fPtr; +}; + + +template +class BExclusiveBorrow { + template + friend class BBorrow; + + T* fPtr = nullptr; + BorrowAdmin* fAdminBlock = nullptr; + +public: + BExclusiveBorrow() noexcept + { + + } + + + BExclusiveBorrow(nullptr_t) noexcept + { + + } + + + BExclusiveBorrow(T* object) + { + fAdminBlock = new BorrowPointer(object); + fPtr = object; + } + + ~BExclusiveBorrow() + { + if (fAdminBlock) + fAdminBlock->Forfeit(); + } + + BExclusiveBorrow(const BExclusiveBorrow&) = delete; + + + BExclusiveBorrow& operator=(const BExclusiveBorrow&) = delete; + + + BExclusiveBorrow(BExclusiveBorrow&& other) noexcept + { + if (fAdminBlock) + fAdminBlock->Forfeit(); + fAdminBlock = other.fAdminBlock; + fPtr = other.fPtr; + other.fAdminBlock = nullptr; + other.fPtr = nullptr; + } + + + BExclusiveBorrow& + operator=(BExclusiveBorrow&& other) noexcept + { + if (fAdminBlock) + fAdminBlock->Forfeit(); + fAdminBlock = other.fAdminBlock; + fPtr = other.fPtr; + other.fAdminBlock = nullptr; + other.fPtr = nullptr; + return *this; + } + + + bool + HasValue() const noexcept + { + return bool(fPtr); + } + + + T& + operator*() const + { + if (fAdminBlock && !fAdminBlock->IsBorrowed()) + return *fPtr; + throw BBorrowError(__PRETTY_FUNCTION__); + } + + + T* + operator->() const + { + if (fAdminBlock && !fAdminBlock->IsBorrowed()) + return fPtr; + throw BBorrowError(__PRETTY_FUNCTION__); + } + + + std::unique_ptr + Release() + { + if (!fAdminBlock) + throw BBorrowError(__PRETTY_FUNCTION__); + fAdminBlock->Release(); + auto retval = std::unique_ptr(fPtr); + fPtr = nullptr; + fAdminBlock = nullptr; + return retval; + } +}; + + +template +class BBorrow { + T* fPtr = nullptr; + BorrowAdmin* fAdminBlock = nullptr; + +public: + BBorrow() noexcept + { + + } + + + BBorrow(nullptr_t) noexcept + { + + } + + + template + explicit BBorrow(BExclusiveBorrow

& owner) + : fPtr(owner.fPtr), fAdminBlock(owner.fAdminBlock) + { + fAdminBlock->Borrow(); + } + + + BBorrow(const BBorrow&) = delete; + + + BBorrow& operator=(const BBorrow&) = delete; + + + BBorrow(BBorrow&& other) noexcept + : fPtr(other.fPtr), fAdminBlock(other.fAdminBlock) + { + other.fPtr = nullptr; + other.fAdminBlock = nullptr; + } + + + BBorrow& + operator=(BBorrow&& other) noexcept + { + if (fAdminBlock) + fAdminBlock->Return(); + + fPtr = other.fPtr; + fAdminBlock = other.fAdminBlock; + other.fPtr = nullptr; + other.fAdminBlock = nullptr; + return *this; + } + + + ~BBorrow() + { + if (fAdminBlock) + fAdminBlock->Return(); + } + + + bool + HasValue() const noexcept + { + return bool(fPtr); + } + + + T& + operator*() const + { + if (fPtr) + return *fPtr; + throw BBorrowError(__PRETTY_FUNCTION__); + } + + + T* + operator->() const + { + if (fPtr) + return fPtr; + throw BBorrowError(__PRETTY_FUNCTION__); + } + + + void + Return() noexcept + { + if (fAdminBlock) + fAdminBlock->Return(); + fAdminBlock = nullptr; + fPtr = nullptr; + } +}; + + +template +BExclusiveBorrow +make_exclusive_borrow(_Args&& ...__args) +{ + auto guardedObject = std::make_unique(std::forward<_Args>(__args)...); + auto retval = BExclusiveBorrow(guardedObject.get()); + guardedObject.release(); + return retval; +} + + +} // namespace Network + +} // namespace BPrivate + +#endif // _B_EXCLUSIVE_BORROW_H diff --git a/src/tests/kits/net/netservices2/ExclusiveBorrowTest.cpp b/src/tests/kits/net/netservices2/ExclusiveBorrowTest.cpp new file mode 100644 index 0000000000..a264165cb6 --- /dev/null +++ b/src/tests/kits/net/netservices2/ExclusiveBorrowTest.cpp @@ -0,0 +1,218 @@ +/* + * Copyright 2022 Haiku Inc. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Niels Sascha Reedijk, niels.reedijk@gmail.com + */ + +#include "ExclusiveBorrowTest.h" + +#include +#include + +#include +#include + +#include + +using BPrivate::Network::BBorrow; +using BPrivate::Network::BBorrowError; +using BPrivate::Network::BExclusiveBorrow; +using BPrivate::Network::make_exclusive_borrow; + + +class DeleteTestHelper +{ +public: + DeleteTestHelper(std::atomic& deleted) + : fDeleted(deleted) + { + + } + + ~DeleteTestHelper() + { + fDeleted.store(true); + } + +private: + std::atomic& fDeleted; +}; + + +class Base { +public: + Base() + { + + } + + + virtual ~Base() + { + + } + + + virtual bool IsDerived() + { + return false; + } +}; + + +class Derived : public Base { +public: + Derived() { + + } + + + virtual ~Derived() { + + } + + + virtual bool IsDerived() override + { + return true; + } +}; + + +ExclusiveBorrowTest::ExclusiveBorrowTest() +{ + +} + + +void +ExclusiveBorrowTest::ObjectDeleteTest() +{ + // Case 1: object never gets borrowed and goes out of scope + std::atomic deleted = false; + { + auto object = make_exclusive_borrow(deleted); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE("(1) Expected object to be deleted", true, deleted.load()); + + // Case 2: object gets borrowed, returned and then goes out of scope + deleted.store(false); + { + auto object = make_exclusive_borrow(deleted); + { + auto borrow = BBorrow(object); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE("(2) Object should not be deleted", false, deleted.load()); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE("(2) Expected object to be deleted", true, deleted.load()); + + // Case 3: object gets borrowed, forfeited and then borrow goes out of scope + deleted.store(false); + { + auto borrow = BBorrow(nullptr); + { + auto object = make_exclusive_borrow(deleted); + borrow = BBorrow(object); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE("(3) Object should not be deleted", false, deleted.load()); + } + CPPUNIT_ASSERT_EQUAL_MESSAGE("(3) Expected object to be deleted", true, deleted.load()); +} + + +void +ExclusiveBorrowTest::OwnershipTest() +{ + auto ownedObject = make_exclusive_borrow(1); + CPPUNIT_ASSERT(*ownedObject == 1); + + auto borrow = BBorrow(ownedObject); + try { + std::ignore = *ownedObject == 1; + CPPUNIT_FAIL("Unexpected access to the owned object while borrowed"); + } catch (const BBorrowError& e) { + // expected + } + + try { + std::ignore = *borrow == 1; + // should succeed + } catch (const BBorrowError& e) { + CPPUNIT_FAIL("Unexpected error accessing the borrowed object"); + } + + try { + auto borrowAgain = BBorrow(ownedObject); + CPPUNIT_FAIL("Unexpectedly able to borrow the owned object again"); + } catch (const BBorrowError& e) { + // expected + } + + try { + borrow = BBorrow(nullptr); + std::ignore = *borrow == 1; + CPPUNIT_FAIL("Unexpected access to an empty borrowed object"); + } catch (const BBorrowError& e) { + // expected + } + + try { + std::ignore = *ownedObject == 1; + } catch (const BBorrowError& e) { + CPPUNIT_FAIL("Unexpected error accessing the owned object"); + } +} + + +void +ExclusiveBorrowTest::PolymorphismTest() +{ + auto owned = make_exclusive_borrow(); + { + auto borrowDerived = BBorrow(owned); + CPPUNIT_ASSERT_EQUAL(true, borrowDerived->IsDerived()); + } + { + auto borrowBase = BBorrow(owned); + CPPUNIT_ASSERT_EQUAL(true, borrowBase->IsDerived()); + } +} + + +void +ExclusiveBorrowTest::ReleaseTest() +{ + auto ownedObject = make_exclusive_borrow(1); + auto ownedPointer = std::addressof(*ownedObject); + try { + auto borrow = BBorrow(ownedObject); + auto invalidClaimedPointer = ownedObject.Release(); + CPPUNIT_FAIL("Unexpectedly able to release a borrowed pointer"); + } catch (const BBorrowError&) { + // expected to fail + } + + auto validClaimedPointer = ownedObject.Release(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Expected released pointer to point to the same object", + validClaimedPointer.get(), ownedPointer); +} + + +/* static */ void +ExclusiveBorrowTest::AddTests(BTestSuite& parent) +{ + CppUnit::TestSuite& suite = *new CppUnit::TestSuite("ExclusiveBorrowTest"); + + suite.addTest(new CppUnit::TestCaller( + "ExclusiveBorrowTest::ObjectDeleteTest", &ExclusiveBorrowTest::ObjectDeleteTest)); + suite.addTest(new CppUnit::TestCaller( + "ExclusiveBorrowTest::OwnershipTest", &ExclusiveBorrowTest::OwnershipTest)); + suite.addTest(new CppUnit::TestCaller( + "ExclusiveBorrowTest::PolymorphismTest", &ExclusiveBorrowTest::PolymorphismTest)); + suite.addTest(new CppUnit::TestCaller( + "ExclusiveBorrowTest::ReleaseTest", &ExclusiveBorrowTest::ReleaseTest)); + + parent.addTest("ExclusiveBorrowTest", &suite); +} diff --git a/src/tests/kits/net/netservices2/ExclusiveBorrowTest.h b/src/tests/kits/net/netservices2/ExclusiveBorrowTest.h new file mode 100644 index 0000000000..ca8c8d27d0 --- /dev/null +++ b/src/tests/kits/net/netservices2/ExclusiveBorrowTest.h @@ -0,0 +1,26 @@ +/* + * Copyright 2021 Haiku, inc. + * Distributed under the terms of the MIT License. + */ +#ifndef EXCLUSIVE_BORROW_TEST_H +#define EXCLUSIVE_BORROW_TEST_H + +#include +#include + + +class ExclusiveBorrowTest: public BTestCase { +public: + ExclusiveBorrowTest(); + + void ObjectDeleteTest(); + void OwnershipTest(); + void PolymorphismTest(); + void ReleaseTest(); + + static void AddTests(BTestSuite& suite); +}; + + + +#endif // EXCLUSIVE_BORROW_TEST_H diff --git a/src/tests/kits/net/netservices2/Jamfile b/src/tests/kits/net/netservices2/Jamfile index 309a14fd3d..f4fba5b74f 100644 --- a/src/tests/kits/net/netservices2/Jamfile +++ b/src/tests/kits/net/netservices2/Jamfile @@ -7,6 +7,7 @@ if $(TARGET_PACKAGING_ARCH) != x86_gcc2 { UnitTestLib netservicekit2test.so : ServicesKitTestAddon.cpp + ExclusiveBorrowTest.cpp HttpDebugLogger.cpp HttpProtocolTest.cpp TestServer.cpp diff --git a/src/tests/kits/net/netservices2/ServicesKitTestAddon.cpp b/src/tests/kits/net/netservices2/ServicesKitTestAddon.cpp index b8aa49620d..645d2dcbed 100644 --- a/src/tests/kits/net/netservices2/ServicesKitTestAddon.cpp +++ b/src/tests/kits/net/netservices2/ServicesKitTestAddon.cpp @@ -7,6 +7,7 @@ #include #include +#include "ExclusiveBorrowTest.h" #include "HttpProtocolTest.h" @@ -15,6 +16,7 @@ getTestSuite() { BTestSuite* suite = new BTestSuite("NetServices2Kit"); + ExclusiveBorrowTest::AddTests(*suite); HttpProtocolTest::AddTests(*suite); HttpIntegrationTest::AddTests(*suite);