From 8bf9ebfd1bcea6b67de39cfd858064d727a03c7c Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Fri, 11 Sep 2020 18:39:57 -0700 Subject: [PATCH] Add null_resource --- doc/qbk/02_01_quick_look.qbk | 5 ++ doc/qbk/02_07_allocators.qbk | 83 ++++++++++++++++------ doc/qbk/main.qbk | 25 +++---- doc/qbk/quickref.xml | 1 + include/boost/json.hpp | 6 ++ include/boost/json/impl/null_resource.ipp | 48 +++++++++++++ include/boost/json/null_resource.hpp | 83 ++++++++++++++++++++++ include/boost/json/src.hpp | 1 + test/Jamfile | 2 + test/doc_allocators.cpp | 85 +++++++++++++++++++++++ test/null_resource.cpp | 45 ++++++++++++ 11 files changed, 349 insertions(+), 35 deletions(-) create mode 100644 include/boost/json/impl/null_resource.ipp create mode 100644 include/boost/json/null_resource.hpp create mode 100644 test/doc_allocators.cpp create mode 100644 test/null_resource.cpp diff --git a/doc/qbk/02_01_quick_look.qbk b/doc/qbk/02_01_quick_look.qbk index 99a0a2ac..fba5650f 100644 --- a/doc/qbk/02_01_quick_look.qbk +++ b/doc/qbk/02_01_quick_look.qbk @@ -144,6 +144,11 @@ storage, improving performance. [doc_quick_look_11] +With strategic use of the right memory resources, parser instance, +and calculated upper limits on buffer sizes, it is possible to parse +and examine JSON without ['any] dynamic memory allocations. This is +explored in more detail in a later section. + [/-----------------------------------------------------------------------------] [h5 Serializing] diff --git a/doc/qbk/02_07_allocators.qbk b/doc/qbk/02_07_allocators.qbk index 3c7e5f1c..569b1c2d 100644 --- a/doc/qbk/02_07_allocators.qbk +++ b/doc/qbk/02_07_allocators.qbk @@ -112,9 +112,9 @@ It is desired to create a single type `T` with the following properties: * `T` supports both shared ownership, and non-ownership * `T` interoperates with code already using `std::pmr` -The __storage_ptr__ smart pointer container used in Boost.JSON builds -and improves upon C++17's memory allocation interfaces, accomplishing -the goals above. As a result, libraries which use this type compose +The __storage_ptr__ used in Boost.JSON builds and improves +upon C++17's memory allocation interfaces, accomplishing the +goals above. As a result, libraries which use this type compose more easily and enjoy faster compilation, as container function definitions can be out-of-line. @@ -124,28 +124,50 @@ definitions can be out-of-line. [section:storage_ptr The __storage_ptr__] -Instances of __value__, __object__, __array__, and __string__ all use -dynamically allocated memory. To allow callers to control the strategy -used to allocate and deallocate memory, the library provides the smart -pointer container __storage_ptr__, similar in function to the C++ standard -library's polymorphic allocator but with the following additional features: +Variable-length containers in this library all use dynamically allocated +memory to store their contents. Callers can gain control over the strategy +used for allocation by specifying a __storage_ptr__ in select constructors +and function parameter lists. A __storage_ptr__ has these properties: -* __storage_ptr__ can either function as a reference counted smart pointer or - a reference wrapper around a __memory_resource__, allowing for the lifetime of - the managed object to be extended. +* A storage pointer always points to a valid, + type-erased __memory_resource__. -* An implementation can indicate to the library that deallocation - is not necessary, allowing destructor calls to be elided in certain situations. +* Default-constructed storage pointers reference the + ['default resource], an implementation-defined instance + which always uses the equivalent of global operator new + and delete. -These types and functions are available: +* Storage pointers constructed from a + [link json.ref.boost__json__memory_resource `memory_resource*`] + or __polymorphic_allocator__ do not acquire ownership; the + caller is responsible for ensuring that the lifetime of + the resource extends until it is no longer referenced. + +* A storage pointer obtained from __make_counted_resource__ + acquires shared ownership of the memory resource; the + lifetime of the resource is extended until all copies + of the storage pointer are destroyed. + +* The storage pointer remembers the value of + __is_deallocate_trivial__ before type-erasing the resource, + allowing the value to be queried at run-time. + +This lists all of the allocation-related types and functions +available when using the library: [table Functions and Types [ [Name] [Description] ] [ + [__is_deallocate_trivial__] + [ + A customization point allowing a memory resource type + to indicate that calls to deallocate are trivial. + ] +][ [__make_counted_resource__] [ - A function that returns a reference-counted storage pointer - with ownership of a new, dynamically allocated memory resource. + A function returning a smart pointer with shared + ownership of a newly allocated memory resource. ] ][ [__memory_resource__] @@ -155,26 +177,41 @@ These types and functions are available: ][ [__monotonic_resource__] [ - A memory resource that allocates zero or more blocks of memory - from which allocations are made; block is twice as large as the last. - Allocated memory is not freed until the resource is destroyed, making - it fast for parsing but not ideal for mutation. + A memory resource which allocates large blocks of memory and + has a trivial deallocate function. Allocated memory is not + freed until the resource is destroyed, making it fast for + parsing but not suited for performing modifications. + ] +][ + [__null_resource__] + [ + A memory resource always throws an exception upon allocation. + This is used to to achieve the invariant that no parsing + or container operation will dynamically allocate memory. + ] +][ + [__polymorphic_allocator__] + [ + An __Allocator__ which uses a reference to a + __memory_resource__ to perform allocations. ] ][ [__static_resource__] [ A memory resource that uses a single caller provided - buffer, from which individual allocations are made. - No dynamic allocations are used. + buffer. No dynamic allocations are used. This is fast for + parsing but not suited for performing modifications. ] ][ [__storage_ptr__] [ - A smart pointer container through which a __memory_resource__ + A smart pointer through which a __memory_resource__ is managed and accessed. ] ]] +[heading Default Resource] + The library provides a ['default memory resource] object which wraps calls to the global allocation and deallocation functions (`operator new` and `operator delete`). This memory resource is not reference counted, and requires calls to deallocate to diff --git a/doc/qbk/main.qbk b/doc/qbk/main.qbk index bd08667f..94c5f8ae 100644 --- a/doc/qbk/main.qbk +++ b/doc/qbk/main.qbk @@ -58,14 +58,14 @@ [def __key_value_pair__ [link json.ref.boost__json__key_value_pair `key_value_pair`]] [def __kind__ [link json.ref.boost__json__kind `kind`]] [def __make_counted_resource__ [link json.ref.boost__json__make_counted_resource `make_counted_resource`]] -[def __number__ [link json.ref.boost__json__number `number`]] +[def __memory_resource__ [link json.ref.boost__json__memory_resource `memory_resource`]] +[def __monotonic_resource__ [link json.ref.boost__json__monotonic_resource `monotonic_resource`]] +[def __null_resource__ [link json.ref.boost__json__null_resource `null_resource`]] [def __object__ [link json.ref.boost__json__object `object`]] [def __parse__ [link json.ref.boost__json__parse `parse`]] [def __parser__ [link json.ref.boost__json__parser `parser`]] [def __parse_options__ [link json.ref.boost__json__parse_options `parse_options`]] [def __polymorphic_allocator__ [link json.ref.boost__json__polymorphic_allocator `polymorphic_allocator`]] -[def __memory_resource__ [link json.ref.boost__json__memory_resource `memory_resource`]] -[def __monotonic_resource__ [link json.ref.boost__json__monotonic_resource `monotonic_resource`]] [def __serialize__ [link json.ref.boost__json__serialize `serialize`]] [def __serializer__ [link json.ref.boost__json__serializer `serializer`]] [def __static_resource__ [link json.ref.boost__json__static_resource `static_resource`]] @@ -95,15 +95,6 @@ [/-----------------------------------------------------------------------------] -[import ../../example/pretty.cpp] -[import ../../example/validate.cpp] -[import ../../include/boost/json/impl/serialize.ipp] -[import ../../test/doc_quick_look.cpp] -[import ../../test/memory_resource.cpp] -[import ../../test/snippets.cpp] - -[/-----------------------------------------------------------------------------] - Boost.JSON is a portable C++ library which implements the [@https://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf JSON Data Interchange Syntax], providing algorithms for parsing and serialization, as well as a robust @@ -149,6 +140,16 @@ in memory. ]] ] +[/-----------------------------------------------------------------------------] + +[import ../../example/pretty.cpp] +[import ../../example/validate.cpp] +[import ../../include/boost/json/impl/serialize.ipp] +[import ../../test/doc_allocators.cpp] +[import ../../test/doc_quick_look.cpp] +[import ../../test/memory_resource.cpp] +[import ../../test/snippets.cpp] + [include 01_overview.qbk] [include 02_00_usage.qbk] [include 03_examples.qbk] diff --git a/doc/qbk/quickref.xml b/doc/qbk/quickref.xml index 93da45b0..14a5dbb1 100644 --- a/doc/qbk/quickref.xml +++ b/doc/qbk/quickref.xml @@ -24,6 +24,7 @@ basic_parser key_value_pair monotonic_resource + null_resource object parser parse_options diff --git a/include/boost/json.hpp b/include/boost/json.hpp index 42b3a692..0d02df75 100644 --- a/include/boost/json.hpp +++ b/include/boost/json.hpp @@ -15,11 +15,15 @@ #include #include #include +#include #include +#include #include #include #include +#include #include +#include #include #include #include @@ -28,6 +32,8 @@ #include #include #include +#include +#include #include // Intentionally excluded diff --git a/include/boost/json/impl/null_resource.ipp b/include/boost/json/impl/null_resource.ipp new file mode 100644 index 00000000..05355ee0 --- /dev/null +++ b/include/boost/json/impl/null_resource.ipp @@ -0,0 +1,48 @@ +// +// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/json +// + +#ifndef BOOST_JSON_IMPL_NULL_RESOURCE_IPP +#define BOOST_JSON_IMPL_NULL_RESOURCE_IPP + +#include +#include + +BOOST_JSON_NS_BEGIN + +void* +null_resource:: +do_allocate( + std::size_t, + std::size_t) +{ + detail::throw_bad_alloc( + BOOST_CURRENT_LOCATION); +} + +void +null_resource:: +do_deallocate( + void*, + std::size_t, + std::size_t) +{ + // do nothing +} + +bool +null_resource:: +do_is_equal( + memory_resource const& mr) const noexcept +{ + return this == &mr; +} + +BOOST_JSON_NS_END + +#endif diff --git a/include/boost/json/null_resource.hpp b/include/boost/json/null_resource.hpp new file mode 100644 index 00000000..5d317e33 --- /dev/null +++ b/include/boost/json/null_resource.hpp @@ -0,0 +1,83 @@ +// +// Copyright (c) 2020 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/json +// + +#ifndef BOOST_JSON_NULL_RESOURCE_HPP +#define BOOST_JSON_NULL_RESOURCE_HPP + +#include +#include + +BOOST_JSON_NS_BEGIN + +//---------------------------------------------------------- + +/** A resource which always fails. + + This memory resource always throws the exception + `std::bad_alloc` in calls to `allocate`. +*/ +class BOOST_JSON_CLASS_DECL + null_resource final + : public memory_resource +{ +public: + /// Copy constructor (deleted) + null_resource( + null_resource const&) = delete; + + /// Copy assignment (deleted) + null_resource& operator=( + null_resource const&) = delete; + + /// Destructor + ~null_resource() noexcept = default; + + /** Constructor + + This constructors the resource. + + @par Complexity + Constant. + + @par Exception Safety + No-throw guarantee. + */ + /** @{ */ + null_resource() noexcept = default; + +protected: +#ifndef BOOST_JSON_DOCS + void* + do_allocate( + std::size_t n, + std::size_t align) override; + + void + do_deallocate( + void* p, + std::size_t n, + std::size_t align) override; + + bool + do_is_equal( + memory_resource const& mr + ) const noexcept override; +#endif +}; + +template<> +struct is_deallocate_trivial< + null_resource> +{ + static constexpr bool value = true; +}; + +BOOST_JSON_NS_END + +#endif diff --git a/include/boost/json/src.hpp b/include/boost/json/src.hpp index ba821dba..216b82b1 100644 --- a/include/boost/json/src.hpp +++ b/include/boost/json/src.hpp @@ -31,6 +31,7 @@ in a translation unit of the program. #include #include #include +#include #include #include #include diff --git a/test/Jamfile b/test/Jamfile index 430b5650..5a58c7c7 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -27,12 +27,14 @@ else local SOURCES = array.cpp basic_parser.cpp + doc_allocators.cpp doc_quick_look.cpp error.cpp json.cpp kind.cpp monotonic_resource.cpp natvis.cpp + null_resource.cpp number_cast.cpp object.cpp parse.cpp diff --git a/test/doc_allocators.cpp b/test/doc_allocators.cpp new file mode 100644 index 00000000..9a1f9f95 --- /dev/null +++ b/test/doc_allocators.cpp @@ -0,0 +1,85 @@ +// +// Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/json +// + +#include + +#include "test_suite.hpp" + +BOOST_JSON_NS_BEGIN + +//[doc_allocators_1 +void do_rpc( string_view s ) +{ + // The parser will use this storage for its temporary needs + unsigned char temp[ 4000 ]; + + // The null resource guarantees we will never dynamically allocate + null_resource mr1; + + // Construct a strict parser using the temp buffer and no dynamic memory + parser p( &mr1, parse_options(), temp ); + + // Now we need a buffer to hold the actual JSON values + unsigned char buf[ 6000 ]; + + // The static resource is monotonic, using only a caller-provided buffer + static_resource mr2( buf ); + + // We need to catch any exceptions thrown by the two memory resources + try + { + // This error code indicates errors not related to memory exhaustion + error_code ec; + + // Parse the entire string we received from the network client + p.write( s, ec ); + + // Inform the parser that the complete input has been provided + if(! ec ) + p.finish( ec ); + + if(! ec ) + { + // Retrieve the value. It will use `buf` for storage. + value jv = p.release(); + + // At this point we can inspect jv and perform the requested RPC. + } + else + { + // An error occurred. A real program would report the error + // message back to the network client, indicating that the + // received JSON was invalid. + } + + } + catch(std::bad_alloc const&) + { + // The memory needed to parse this JSON exceeded our statically + // define upper limits. A real program would send an error message + // back to the network client informing that their JSON is too large. + } +} +//] + +//---------------------------------------------------------- + +class doc_allocators_test +{ +public: + void + run() + { + BOOST_TEST_PASS(); + } +}; + +TEST_SUITE(doc_allocators_test, "boost.json.doc_allocators"); + +BOOST_JSON_NS_END diff --git a/test/null_resource.cpp b/test/null_resource.cpp new file mode 100644 index 00000000..eedeb972 --- /dev/null +++ b/test/null_resource.cpp @@ -0,0 +1,45 @@ +// +// Copyright (c) 2020 Vinnie Falco (vinnie.falco@gmail.com) +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// Official repository: https://github.com/cppalliance/json +// + +// Test that header file is self-contained. +#include + +#include "test_suite.hpp" + +BOOST_JSON_NS_BEGIN + +class null_resource_test +{ +public: + void + test() + { + null_resource mr; + BOOST_TEST_THROWS( + mr.allocate(16), + std::bad_alloc); + char buf[128]; + // no-op + mr.deallocate(&buf[0], 128); + + BOOST_TEST(mr == mr); + null_resource mr2; + BOOST_TEST(mr != mr2); + } + + void + run() + { + test(); + } +}; + +TEST_SUITE(null_resource_test, "boost.json.null_resource"); + +BOOST_JSON_NS_END