From b2ea04995240a9f8d6f8b0f4f47d05e95ec953ce Mon Sep 17 00:00:00 2001 From: evoskuil Date: Tue, 7 Jan 2025 15:30:50 -0500 Subject: [PATCH] Update storage interface, impl, test to support dynamic head. --- .../database/memory/interfaces/storage.hpp | 4 + include/bitcoin/database/memory/map.hpp | 4 + src/memory/map.cpp | 38 ++- test/memory/map.cpp | 251 +++++++++++++++++- test/mocks/chunk_storage.cpp | 16 ++ test/mocks/chunk_storage.hpp | 2 + 6 files changed, 304 insertions(+), 11 deletions(-) diff --git a/include/bitcoin/database/memory/interfaces/storage.hpp b/include/bitcoin/database/memory/interfaces/storage.hpp index c508346d..f116ffe4 100644 --- a/include/bitcoin/database/memory/interfaces/storage.hpp +++ b/include/bitcoin/database/memory/interfaces/storage.hpp @@ -67,6 +67,10 @@ class storage /// Allocate bytes and return offset to first allocated (or eof). virtual size_t allocate(size_t chunk) NOEXCEPT = 0; + /// Get remap-protected r/w access to offset (or null) allocated to size. + virtual memory_ptr set(size_t offset, size_t size, + uint8_t backfill) NOEXCEPT = 0; + /// Get remap-protected r/w access to start/offset of memory map (or null). virtual memory_ptr get(size_t offset=zero) const NOEXCEPT = 0; diff --git a/include/bitcoin/database/memory/map.hpp b/include/bitcoin/database/memory/map.hpp index 43eb3a5f..54a67ce0 100644 --- a/include/bitcoin/database/memory/map.hpp +++ b/include/bitcoin/database/memory/map.hpp @@ -90,6 +90,10 @@ class BCD_API map /// Allocate bytes and return offset to first allocated (or eof). size_t allocate(size_t chunk) NOEXCEPT override; + /// Get remap-protected r/w access to offset (or null) allocated to size. + memory_ptr set(size_t offset, size_t size, + uint8_t backfill) NOEXCEPT override; + /// Get remap-protected r/w access to start/offset of memory map (or null). memory_ptr get(size_t offset=zero) const NOEXCEPT override; diff --git a/src/memory/map.cpp b/src/memory/map.cpp index be7a6c20..0c34eeca 100644 --- a/src/memory/map.cpp +++ b/src/memory/map.cpp @@ -242,13 +242,13 @@ size_t map::allocate(size_t chunk) NOEXCEPT auto end = logical_ + chunk; if (end > capacity_) { - const auto size = to_capacity(end); + const auto new_capacity = to_capacity(end); // TODO: Could loop over a try lock here and log deadlock warning. std::unique_lock remap_lock(remap_mutex_); // Disk full condition leaves store in valid state despite eof return. - if (!remap_(size)) + if (!remap_(new_capacity)) return storage::eof; } @@ -256,6 +256,40 @@ size_t map::allocate(size_t chunk) NOEXCEPT return end; } +memory_ptr map::set(size_t offset, size_t size, uint8_t backfill) NOEXCEPT +{ + { + std::unique_lock field_lock(field_mutex_); + + if (fault_ || !loaded_ || is_add_overflow(offset, size)) + return {}; + + const auto end = std::max(logical_, offset + size); + if (end > capacity_) + { + const auto old_capacity = capacity_; + const auto new_capacity = to_capacity(end); + + // TODO: Could loop over a try lock here and log deadlock warning. + std::unique_lock remap_lock(remap_mutex_); + + // Disk full condition leaves store in valid state despite null. + if (!remap_(new_capacity)) + return {}; + + // Fill new capacity as offset may not be at end due to expansion. + BC_PUSH_WARNING(NO_POINTER_ARITHMETIC) + std::fill_n(memory_map_ + old_capacity, new_capacity - old_capacity, + backfill); + BC_POP_WARNING() + } + + logical_ = end; + } + + return get(offset); +} + memory_ptr map::get(size_t offset) const NOEXCEPT { // Obtaining logical before access prevents mutual mutex wait (deadlock). diff --git a/test/memory/map.cpp b/test/memory/map.cpp index 4c87213c..f40ed758 100644 --- a/test/memory/map.cpp +++ b/test/memory/map.cpp @@ -77,6 +77,7 @@ BOOST_AUTO_TEST_CASE(map__close__open__true) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(instance.is_open()); @@ -88,6 +89,7 @@ BOOST_AUTO_TEST_CASE(map__close__closed__true) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.is_open()); BOOST_REQUIRE(!instance.close()); @@ -97,6 +99,7 @@ BOOST_AUTO_TEST_CASE(map__close__loaded__false) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); @@ -169,6 +172,7 @@ BOOST_AUTO_TEST_CASE(map__load__unloaded__true) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); @@ -181,12 +185,15 @@ BOOST_AUTO_TEST_CASE(map__load__shared__false) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); auto memory = instance.get(instance.allocate(1)); + BOOST_REQUIRE(memory); BOOST_REQUIRE_EQUAL(instance.load(), error::load_locked); + memory.reset(); BOOST_REQUIRE(!instance.unload()); BOOST_REQUIRE(!instance.close()); @@ -197,6 +204,7 @@ BOOST_AUTO_TEST_CASE(map__load__loaded__false) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); @@ -210,6 +218,7 @@ BOOST_AUTO_TEST_CASE(map__unload__unloaded__true) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.unload()); @@ -221,6 +230,7 @@ BOOST_AUTO_TEST_CASE(map__unload__loaded__true) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); @@ -232,9 +242,10 @@ BOOST_AUTO_TEST_CASE(map__unload__loaded__true) BOOST_AUTO_TEST_CASE(map__capacity__default__expected) { - constexpr auto default_minimum_capacity = 1u; + constexpr auto default_minimum_capacity = 1_size; const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE_EQUAL(instance.capacity(), zero); @@ -249,6 +260,7 @@ BOOST_AUTO_TEST_CASE(map__truncate__unloaded__false) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.truncate(42)); @@ -279,6 +291,7 @@ BOOST_AUTO_TEST_CASE(map__allocate__unloaded__false) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE_EQUAL(instance.allocate(42), map::eof); @@ -288,13 +301,18 @@ BOOST_AUTO_TEST_CASE(map__allocate__unloaded__false) BOOST_AUTO_TEST_CASE(map__allocate__loaded__expected_capacity) { - constexpr auto size = 100u; + constexpr auto half_rate = 50_size; + constexpr auto minimum = 42_size; + constexpr auto size = 100_size; const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); - map instance(file, 1, 50); + + map instance(file, minimum, half_rate); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); + BOOST_REQUIRE_EQUAL(instance.capacity(), minimum); BOOST_REQUIRE_EQUAL(instance.allocate(size), zero); + constexpr auto capacity = size + to_half(size); BOOST_REQUIRE_EQUAL(instance.capacity(), capacity); BOOST_REQUIRE(!instance.unload()); @@ -306,6 +324,7 @@ BOOST_AUTO_TEST_CASE(map__allocate__add_overflow__eof) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); @@ -318,14 +337,16 @@ BOOST_AUTO_TEST_CASE(map__allocate__add_overflow__eof) BOOST_AUTO_TEST_CASE(map__allocate__minimum_no_expansion__expected_capacity) { - constexpr auto size = 42u; - constexpr auto minimum = 100u; + constexpr auto size = 42_size; + constexpr auto minimum = 100_size; const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file, minimum, 0); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); BOOST_REQUIRE_EQUAL(instance.allocate(size), zero); + constexpr auto capacity = std::max(minimum, size); BOOST_REQUIRE_EQUAL(instance.capacity(), capacity); BOOST_REQUIRE(!instance.unload()); @@ -336,10 +357,9 @@ BOOST_AUTO_TEST_CASE(map__allocate__minimum_no_expansion__expected_capacity) BOOST_AUTO_TEST_CASE(map__allocate__no_minimum_expansion__expected_capacity) { // map will fail if minimum is zero. - constexpr auto minimum = 1u; - constexpr auto rate = 42u; - constexpr auto size = 100u; - + constexpr auto minimum = 1_size; + constexpr auto rate = 42_size; + constexpr auto size = 100_size; const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); @@ -356,10 +376,212 @@ BOOST_AUTO_TEST_CASE(map__allocate__no_minimum_expansion__expected_capacity) BOOST_REQUIRE(!instance.get_fault()); } +BOOST_AUTO_TEST_CASE(map__set__unloaded__false) +{ + const std::string file = TEST_PATH; + BOOST_REQUIRE(test::create(file)); + map instance(file); + BOOST_REQUIRE(!instance.open()); + BOOST_REQUIRE(!instance.set(42, 24, 0xff)); + BOOST_REQUIRE(!instance.close()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(map__set__loaded__expected_capacity) +{ + constexpr auto half_rate = 50_size; + constexpr auto minimum = 42_size; + constexpr auto size = 100_size; + constexpr auto offset = 42_size; + constexpr auto fill = 0b01010101; + const std::string file = TEST_PATH; + BOOST_REQUIRE(test::create(file)); + + map instance(file, minimum, half_rate); + BOOST_REQUIRE(!instance.open()); + BOOST_REQUIRE(!instance.load()); + BOOST_REQUIRE_EQUAL(instance.capacity(), minimum); + + auto memory = instance.set(offset, size, fill); + BOOST_REQUIRE(memory); + + const auto expected = std::next(instance.get()->data(), offset); + BOOST_REQUIRE(memory->data() == expected); + + constexpr auto capacity = offset + size + to_half(offset + size); + BOOST_REQUIRE_EQUAL(instance.capacity(), capacity); + BOOST_REQUIRE_EQUAL(instance.unload(), error::unload_locked); + + memory.reset(); + BOOST_REQUIRE(!instance.unload()); + BOOST_REQUIRE(!instance.close()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(map__set__add_overflow__eof) +{ + const std::string file = TEST_PATH; + BOOST_REQUIRE(test::create(file)); + + map instance(file); + BOOST_REQUIRE(!instance.open()); + BOOST_REQUIRE(!instance.load()); + BOOST_REQUIRE(instance.set(100, 24, 0xff)); + BOOST_REQUIRE(!instance.set(max_size_t, 24, 0xff)); + BOOST_REQUIRE(!instance.unload()); + BOOST_REQUIRE(!instance.close()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(map__set__minimum_no_expansion__expected_capacity) +{ + constexpr auto rate = 0_size; + constexpr auto minimum = 42_size; + constexpr auto size = 100_size; + constexpr auto offset = 42_size; + constexpr auto fill = 0b01010101; + const std::string file = TEST_PATH; + BOOST_REQUIRE(test::create(file)); + + map instance(file, minimum, rate); + BOOST_REQUIRE(!instance.open()); + BOOST_REQUIRE(!instance.load()); + BOOST_REQUIRE(instance.set(offset, size, fill)); + + constexpr auto capacity = std::max(minimum, offset + size); + BOOST_REQUIRE_EQUAL(instance.capacity(), capacity); + BOOST_REQUIRE(!instance.unload()); + BOOST_REQUIRE(!instance.close()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(map__set__no_minimum_expansion__expected_capacity) +{ + // map will fail if minimum is zero. + constexpr auto rate = 42_size; + constexpr auto minimum = 1_size; + constexpr auto size = 51_size; + constexpr auto offset = 49_size; + constexpr auto fill = 0b01010101; + const std::string file = TEST_PATH; + BOOST_REQUIRE(test::create(file)); + + map instance(file, minimum, rate); + BOOST_REQUIRE(!instance.open()); + BOOST_REQUIRE(!instance.load()); + BOOST_REQUIRE(instance.set(offset, size, fill)); + + // These add only because offset + size is 100. + constexpr auto capacity = offset + size + rate; + BOOST_REQUIRE_EQUAL(instance.capacity(), capacity); + BOOST_REQUIRE(!instance.unload()); + BOOST_REQUIRE(!instance.close()); + BOOST_REQUIRE(!instance.get_fault()); +} + +BOOST_AUTO_TEST_CASE(map__set__loaded__expected_fill) +{ + constexpr auto half_rate = 50_size; + constexpr auto minimum = 1_size; + constexpr auto size1 = 3_size; + constexpr auto offset1 = 5_size; + constexpr auto fill1 = 0b0101'0101; + const std::string file = TEST_PATH; + BOOST_REQUIRE(test::create(file)); + + map instance(file, minimum, half_rate); + BOOST_REQUIRE(!instance.open()); + BOOST_REQUIRE(!instance.load()); + + auto memory = instance.set(offset1, size1, fill1); + BOOST_REQUIRE(memory); + + constexpr auto capacity = offset1 + size1 + to_half(offset1 + size1); + BOOST_REQUIRE_EQUAL(instance.capacity(), capacity); + BOOST_REQUIRE_EQUAL(capacity, 12u); + + auto data = instance.get()->data(); + ////BOOST_REQUIRE_EQUAL(data[ 0], 0x00_u8); // cannot assume mmap default fill + ////BOOST_REQUIRE_EQUAL(data[ 1], 0x00_u8); // cannot assume mmap default fill + ////BOOST_REQUIRE_EQUAL(data[ 2], 0x00_u8); // cannot assume mmap default fill + ////BOOST_REQUIRE_EQUAL(data[ 3], 0x00_u8); // cannot assume mmap default fill + ////BOOST_REQUIRE_EQUAL(data[ 4], 0x00_u8); // cannot assume mmap default fill + + BOOST_REQUIRE_EQUAL(data[ 5], fill1); // offset + 0 + BOOST_REQUIRE_EQUAL(data[ 6], fill1); // offset + 1 + BOOST_REQUIRE_EQUAL(data[ 7], fill1); // offset + 2 + BOOST_REQUIRE_EQUAL(data[ 8], fill1); // expansion + BOOST_REQUIRE_EQUAL(data[ 9], fill1); // expansion + BOOST_REQUIRE_EQUAL(data[10], fill1); // expansion + BOOST_REQUIRE_EQUAL(data[11], fill1); // sub1(offset1 + size1 + to_half(offset1 + size1)) + + data[5] = 'a'; + data[6] = 'b'; + data[7] = 'c'; + + constexpr auto size2 = 5_size; + constexpr auto offset2 = 15_size; + constexpr auto fill2 = 0b1111'0000; + memory.reset(); + memory = instance.set(offset2, size2, fill2); + BOOST_REQUIRE(memory); + + constexpr auto capacity2 = offset2 + size2 + to_half(offset2 + size2); + BOOST_REQUIRE_EQUAL(instance.capacity(), capacity2); + BOOST_REQUIRE_EQUAL(capacity2, 30u); + + data[15] = 'd'; + data[16] = 'e'; + data[17] = 'f'; + data[18] = 'g'; + data[19] = 'h'; + + // Get data again in case it has been remapped by set(). + data = instance.get()->data(); + ////BOOST_REQUIRE_EQUAL(data[ 0], 0x00_u8); // cannot assume mmap default fill + ////BOOST_REQUIRE_EQUAL(data[ 1], 0x00_u8); // cannot assume mmap default fill + ////BOOST_REQUIRE_EQUAL(data[ 2], 0x00_u8); // cannot assume mmap default fill + ////BOOST_REQUIRE_EQUAL(data[ 3], 0x00_u8); // cannot assume mmap default fill + ////BOOST_REQUIRE_EQUAL(data[ 4], 0x00_u8); // cannot assume mmap default fill + + BOOST_REQUIRE_EQUAL(data[ 5], 'a'); // offset + 0 + BOOST_REQUIRE_EQUAL(data[ 6], 'b'); // offset + 1 + BOOST_REQUIRE_EQUAL(data[ 7], 'c'); // offset + 2 + BOOST_REQUIRE_EQUAL(data[ 8], fill1); // expansion + BOOST_REQUIRE_EQUAL(data[ 9], fill1); // expansion + BOOST_REQUIRE_EQUAL(data[10], fill1); // expansion + BOOST_REQUIRE_EQUAL(data[11], fill1); // sub1(offset + size + to_half(offset + size)) + + BOOST_REQUIRE_EQUAL(data[12], fill2); // cannot assume mmap default fill + BOOST_REQUIRE_EQUAL(data[13], fill2); // cannot assume mmap default fill + BOOST_REQUIRE_EQUAL(data[14], fill2); // cannot assume mmap default fill + BOOST_REQUIRE_EQUAL(data[15], 'd'); // offset2 + 0 + BOOST_REQUIRE_EQUAL(data[16], 'e'); // offset2 + 1 + BOOST_REQUIRE_EQUAL(data[17], 'f'); // offset2 + 2 + BOOST_REQUIRE_EQUAL(data[18], 'g'); // offset2 + 3 + BOOST_REQUIRE_EQUAL(data[19], 'h'); // offset2 + 4 + BOOST_REQUIRE_EQUAL(data[20], fill2); // expansion + BOOST_REQUIRE_EQUAL(data[21], fill2); // expansion + BOOST_REQUIRE_EQUAL(data[22], fill2); // expansion + BOOST_REQUIRE_EQUAL(data[23], fill2); // expansion + BOOST_REQUIRE_EQUAL(data[24], fill2); // expansion + BOOST_REQUIRE_EQUAL(data[25], fill2); // expansion + BOOST_REQUIRE_EQUAL(data[26], fill2); // expansion + BOOST_REQUIRE_EQUAL(data[27], fill2); // expansion + BOOST_REQUIRE_EQUAL(data[28], fill2); // expansion + BOOST_REQUIRE_EQUAL(data[29], fill2); // expansion + + memory.reset(); + BOOST_REQUIRE(!instance.unload()); + BOOST_REQUIRE(!instance.close()); + BOOST_REQUIRE(!instance.get_fault()); +} + BOOST_AUTO_TEST_CASE(map__get__unloaded__false) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.get()); @@ -371,6 +593,7 @@ BOOST_AUTO_TEST_CASE(map__get__loaded__success) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); @@ -384,6 +607,7 @@ BOOST_AUTO_TEST_CASE(map__flush__unloaded__false) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE_EQUAL(instance.flush(), error::flush_unloaded); @@ -395,6 +619,7 @@ BOOST_AUTO_TEST_CASE(map__flush__loaded__true) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); @@ -409,17 +634,22 @@ BOOST_AUTO_TEST_CASE(map__write__read__expected) constexpr uint64_t expected = 0x0102030405060708_u64; const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); + auto memory = instance.get(instance.allocate(sizeof(uint64_t))); BOOST_REQUIRE(memory); + system::unsafe_to_little_endian(memory->begin(), expected); memory.reset(); BOOST_REQUIRE(!instance.flush()); + memory = instance.get(); BOOST_REQUIRE(memory); BOOST_REQUIRE_EQUAL(system::unsafe_from_little_endian(memory->begin()), expected); + memory.reset(); BOOST_REQUIRE(!instance.unload()); BOOST_REQUIRE(!instance.close()); @@ -430,12 +660,15 @@ BOOST_AUTO_TEST_CASE(map__unload__shared__false) { const std::string file = TEST_PATH; BOOST_REQUIRE(test::create(file)); + map instance(file); BOOST_REQUIRE(!instance.open()); BOOST_REQUIRE(!instance.load()); + auto memory = instance.get(instance.allocate(1)); BOOST_REQUIRE(memory); BOOST_REQUIRE_EQUAL(instance.unload(), error::unload_locked); + memory.reset(); BOOST_REQUIRE(!instance.unload()); BOOST_REQUIRE(!instance.close()); diff --git a/test/mocks/chunk_storage.cpp b/test/mocks/chunk_storage.cpp index 8fb71b20..e0ccbc29 100644 --- a/test/mocks/chunk_storage.cpp +++ b/test/mocks/chunk_storage.cpp @@ -18,6 +18,7 @@ */ #include "../test.hpp" #include "chunk_storage.hpp" +#include #include #include #include @@ -120,6 +121,21 @@ size_t chunk_storage::allocate(size_t chunk) NOEXCEPT return link; } +memory_ptr chunk_storage::set(size_t offset, size_t size, + uint8_t backfill) NOEXCEPT +{ + std::unique_lock field_lock(field_mutex_); + if (system::is_add_overflow(offset, size)) + return {}; + + std::unique_lock map_lock(map_mutex_); + const auto minimum = offset + size; + if (minimum > buffer_.size()) + buffer_.resize(minimum, backfill); + + return get(offset); +} + memory_ptr chunk_storage::get(size_t offset) const NOEXCEPT { const auto ptr = std::make_shared>(map_mutex_); diff --git a/test/mocks/chunk_storage.hpp b/test/mocks/chunk_storage.hpp index 6564889d..4f5c2166 100644 --- a/test/mocks/chunk_storage.hpp +++ b/test/mocks/chunk_storage.hpp @@ -49,6 +49,8 @@ class chunk_storage size_t size() const NOEXCEPT override; bool truncate(size_t size) NOEXCEPT override; size_t allocate(size_t chunk) NOEXCEPT override; + memory_ptr set(size_t offset, size_t size, + uint8_t backfill) NOEXCEPT override; memory_ptr get(size_t offset=zero) const NOEXCEPT override; memory::iterator get_raw(size_t offset=zero) const NOEXCEPT override; code get_fault() const NOEXCEPT override;