Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
sparse_shm_object.hpp
Go to the documentation of this file.
1/* Flow-IPC: Shared Memory
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#pragma once
20
22#include <boost/interprocess/shared_memory_object.hpp>
23
24namespace ipc::shm::bipc_ext
25{
26
27// Types.
28
29/**
30 * Similar to bipc's `shared_memory_object`, but truncate() will not unconditionally commit the entire specified
31 * chunk, while the added commit() method will do so on-demand.
32 *
33 * @todo bipc_ext::Sparse_shm_object is currently in detail/ (formally not user-facing), but it is (1) potentially
34 * fairly useful even by itself and (2) fully compliant with boost.ipc ways of doing things/duck-wise matches
35 * `bipc::shared_memory_object`; so making it publicly available as a utility/bipc extension would be a net win.
36 * As of this writing it hasn't been done for reasons of expediency only. This move would fit well with
37 * doing a similar to-do on Sparse_managed_shm; an example of "synergy" is that the latter uses Sparse_shm_object.
38 * (However that "similar to-do on `Sparse_managed_shm`" involves some light design and coding, while the present
39 * to-do is pretty much "move it out of detail/ and accordingly between `_fwd.hpp`s." Suggest doing
40 * both ~simultaneously. While Sparse_shm_object would be publicly "potentially fairly useful," we estimate
41 * Sparse_managed_shm would be significantly more so, as `basic_managed_shared_memory` is a crown jewel of
42 * usefulness in bipc, and among power users the lack of sparse/on-demand RAM-commits in later Boosts can be
43 * surprising and unpleasant.)
44 *
45 * ### Background ###
46 * `bipc::shared_memory_object`, whose documentation you should read before diving in here, is essentially
47 * an OO representation of a SHM-pool handle as obtained from, at least, the POSIX-SHM `shm_open()`. As such
48 * it performs the essential tasks of opening and/or creating the (POSIX) SHM-object (on construct) and closing it (on
49 * destruction/overwrite/move-from); and truncation (sizing) to a given size.
50 *
51 * The basic *truncation* operation, as via POSIX `ftruncate()`, when applied to a SHM-object is classically
52 * the thing that sizes the SHM area (future SHM-pool) upon having *created* the named SHM-object. (If an existing
53 * one was merely opened, then one usually does not truncate.) At least in Linux this does not commit the RAM
54 * for the entire pool size, meaning all the pages are not taken from actual RAM, such that others cannot use
55 * that actual RAM. In many cases, for larger pools, this is a *good* thing: RAM is committed, page by page, only
56 * when it is touched subsequently. So a 1Gi-sized (truncated) pool won't take a gig of RAM right away; and one
57 * can be less stingy with the size argument, if subsequent SHM-pool use only touches the amount of space it
58 * actually needs, and that amount isn't so big. We can call this *sparse* use of a SHM-object.
59 *
60 * `shared_memory_object::truncate()` performs this truncation operation. Up to (not including) Boost-1.76,
61 * its `truncate()` assumed sparse use by default and did just the above essentially. However this also meant
62 * that if one happens to exceed at least Linux's limit on committed SHM-RAM (the configurable limit on
63 * the tmpfs used-size), subsequently when writing into some page, suddenly the OS has no choice and crashes
64 * the process with SIGBUS with no real possibility of recovery. It would be just writing into a vaddr, so there
65 * is no system-call to error-out, so that's that.
66 *
67 * In Boost-1.76 (pull request https://github.com/boostorg/interprocess/pull/106) this was "fixed" by
68 * unconditionally, in a capable Linux at least, having this `truncate()` method also *commit* the entire
69 * truncated-size. Thus, the SHM-pool is no longer *sparse* -- from the very start. This involves a particular
70 * native call (`posix_fallocate()` et al); and if at that moment the aforementioned limit is exceeded, the
71 * API gracefully emits a no-space error (from native error ENOSPC). So the failure is early/graceful, but also
72 * if the plan wasn't to necessarily use that entire gigabyte of RAM, this approach fully breaks that plan.
73 *
74 * Ideally one would be able to use a SHM-object in either manner.
75 *
76 * @note While the above background leans on the Linux case, and Linux is as of this writing our only target
77 * (with tentative plans at least for macOS/ARM64), it is at least conceivable that conceptually the
78 * same ideas apply and are implementable. So if, after this writing sometime, `*this` is ported to more
79 * OS, the above explanation still applies. I.e., in that hypothetical/conceivable future, truncate() and
80 * commit() should still make *portable* sense (even though the cited Boost history and Linux-specific
81 * mentions do not directly apply).
82 *
83 * ### How this class helps ###
84 * Thus in this extension of `shared_memory_object`:
85 * - truncate() will only truncate (as it used to in earlier Boosts); it will not commit.
86 * SHM-pool sparse use is possible subsequently.
87 * - commit() -- new API -- will commit the currently-set size (as set by the last truncate() if any),
88 * emitting an error as explained above if needed. On success SHM-pool sparse use, in that area, goes away
89 * subsequently. (There are other techniques subsequently available, natively at least, to "un-commit" areas
90 * a/k/a punch holes.)
91 *
92 * Otherwise it behaves identically to `shared_memory_object`.
93 *
94 * @note The default `truncate()` is available through the super-class/casting; but we'd recommend against its use:
95 * In one Boost it'll do what you want (presumably equivalent of new truncate() + commit()), but in another
96 * it won't (equivalent of new truncate() only). Instead just do new truncate() + commit().
97 *
98 * ### How to use this class ###
99 * Use it same as `shared_memory_object` while knowing that truncate() acts as explained above, and that commit() can
100 * optionally be used as explained above. While the ctors are not explicitly declared/documented, they are
101 * inherited fully from base `shared_memory_object`. Move semantics are identical. This sub-class also adds no data
102 * into a `*this`.
103 *
104 * ### Known use cases ###
105 * The conceptual use-case is hopefully adequately explained above. As for where one might instantiate
106 * an actual `*this`:
107 * - One can do it directly. Using bipc's `shared_memory_object` or this extension is easier and more portable
108 * than native `shm_open()` + `ftruncate()` + `posix_fallocate()` and the like. (One still has to then
109 * map it to vaddrs (`mapped_region`), typically, and possibly other operations. Regardless, though,
110 * the SHM-object creation/truncation/closing are more convenient by using a `*this` rather than natively.)
111 * - One can do it as part of higher-level bipc/bipc-like objects. Namely consider `basic_managed_shared_memory`
112 * in bipc. While, as of this writing, it cannot be directly configured to use `Sparse_shm_object` instead
113 * of its hard-coded use of `shared_memory_object`, nevertheless one can develop a class with similar or identical
114 * semantics to `basic_managed_shared_memory` (an otherwise hugely powerful class template) but that allows
115 * for sparse-style use if desired. (It too might gain a method like `commit()`, forwarding to our commit(),
116 * if one wants to go to non-sparse style after all; but it would not be mandatory to call it.)
117 * - See our containing namespace shm::bipc_ext which may contain such a
118 * sparse-capable `basic_managed_shared_memory` variation.
119 */
120class Sparse_shm_object : public bipc::shared_memory_object // It's movable, not copyable.
121{
122public:
123 // Types.
124
125 /// Short-hand for our base.
126 using Base = bipc::shared_memory_object;
127
128 // Constructors/destructor.
129
130 /* Gain (at least) default, create, open, create-or-open ctors + move-ct. Move-assignment is also available.
131 * Reminder that the SHM-object-handle-opening ctors throw on error in bipc style. */
132 using Base::Base;
133
134 // Methods.
135
136 /**
137 * Hides `Base::truncate()`, which is not `virtual`; performs the *truncate* op; does not perform commit op.
138 * Please see class doc header for background/explanation.
139 *
140 * The size last (successfully) set here shall determine how much RAM is committed by subsequent commit() if any.
141 *
142 * Error semantics: bipc-style (throws bipc exception on error).
143 *
144 * @note This method is named the same as the #Base method because (1) it is accurate, and (2) it is easier to
145 * develop a sparse-RAM `basic_managed_shared_memory` equivalent by plugging in Sparse_shm_object instead
146 * of `shared_memory_object`.
147 *
148 * @param length
149 * See shadowed method.
150 */
151 void truncate(bipc::offset_t length);
152
153 /**
154 * Commit (take RAM for) every page of the SHM-object's size as determined by the last truncate().
155 *
156 * Error semantics: bipc-style (throws bipc exception on error).
157 *
158 * @warning Be potentially ready for this to throw a no-space-left exception (Linux: encapsulates ENOSPC), if
159 * the attempt would exhaust a system memory limit (Linux: configurable limit for tmpfs use; might
160 * default to half of physical RAM or, in containers, something like 64Mi bytes).
161 */
162 void commit();
163}; // class Sparse_shm_object
164
165} // namespace ipc::shm::bipc_ext
Similar to bipc's shared_memory_object, but truncate() will not unconditionally commit the entire spe...
bipc::shared_memory_object Base
Short-hand for our base.
void truncate(bipc::offset_t length)
Hides Base::truncate(), which is not virtual; performs the truncate op; does not perform commit op.
void commit()
Commit (take RAM for) every page of the SHM-object's size as determined by the last truncate().
Extensions, informal or otherwise, of boost.ipc (boost::interprocess, bipc) SHM-related facilities,...