A platform for high-performance distributed tool and library development written in C++. It can be deployed in two different cluster modes: standalone or distributed. API for v0.5.0, released on June 13, 2018.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros
InterfaceFunctions.h
Go to the documentation of this file.
1 /*****************************************************************************
2  * *
3  * Copyright 2018 Rice University *
4  * *
5  * Licensed under the Apache License, Version 2.0 (the "License"); *
6  * you may not use this file except in compliance with the License. *
7  * You may obtain a copy of the License at *
8  * *
9  * http://www.apache.org/licenses/LICENSE-2.0 *
10  * *
11  * Unless required by applicable law or agreed to in writing, software *
12  * distributed under the License is distributed on an "AS IS" BASIS, *
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
14  * See the License for the specific language governing permissions and *
15  * limitations under the License. *
16  * *
17  *****************************************************************************/
18 
19 #ifndef INTERFACE_FUNCTIONS_H
20 #define INTERFACE_FUNCTIONS_H
21 
22 #include <cstddef>
23 #include <iostream>
24 #include <vector>
25 #include <algorithm>
26 #include <iterator>
27 #include <cstring>
28 #include "Object.h"
29 
30 namespace pdb {
31 
32 template <class ObjType>
34 template <class ObjType>
35 class Record;
36 template <class ObjType>
37 class Handle;
38 
39 // The way that the whole object infrastructure works is that all Objects are
40 // allocated using the Allocator class. The Allocator class has a notion of
41 // a "current allocation block" which is a block of memory that all allocations
42 // happen from. There is only one active "current allocation block". This
43 // block is set up using the makeObjectAllocatorBlock () function.
44 //
45 // When makeObjectAllocatorBlock () is called many times in succession, it
46 // causes the allocation blocks that used to be active to become inactive.
47 // Whenever an inactive allocation block has no more reachable objects in it,
48 // it is automatically deallocated, so the user never has to worry about memory
49 // leaks, as long as the interface is used correctly. For example, consider
50 // the following code:
51 //
52 // makeObjectAllocatorBlock (1024, false);
53 //
54 // for (int i = 0; i < 100; i++) {
55 // Handle <Employee> myEmp = makeObject <Employee> (100, 120);
56 // Handle <Object> foo = makeObject <Supervisor> (134, 124.5, myEmp);
57 // makeObjectAllocatorBlock (1024, false);
58 // }
59 //
60 // What will happen here is that before the loop, we'll have created an
61 // allocation block. In the zero'th iteration of the loop, myEmp and foo
62 // will be written to that block.
63 //
64 // At the call to makeObjectAllocatorBlock () at the end of the loop, the
65 // current, active allocation block (storing myEmp and foo) becomes inactive.
66 // At the end of the loop, the destructors for myEmp and foo are called.
67 // This causes that inactive block to have no reachable objects, so it is
68 // automatically deleted.
69 //
70 // The new, active allocation block is then used in the next iteration to
71 // store the next incarnations of the objects pointed to by myEmp and foo.
72 // But at the next iteration, the same thing will happen again.
73 //
74 // The result of all of this is that after the loop executes, there will
75 // be no remaining inactive allocation blocks; just a single active block
76 // (the last one created) and that block will have no Objects stored in
77 // it.
78 //
79 // The first parameter to makeObjectAllocatorBlock (1024, false) is the number of bytes to
80 // allocate to write objects to. The second parameter is how to handle makeObject ()
81 // calls that fail due to lack of memory. If throwExceptionOnFail == true, then an
82 // exception is thrown when an allocate fails. If throwExceptionOnFail == false, then
83 // the resulting object will be equal to the nullptr constant.
84 //
85 // Note that while using exceptions are a bit of a pain in the arse, it is MUCH safer
86 // to use throwExceptionOnFail == true as oppsed to == false. The reason is that
87 // it is easily for operations (such as deep copies to the allocation block) to run out
88 // of RAM half way through, in a way that is not transparent to th user. If this
89 // happens, the ultimate return val may be OK, but there can be null pointers embedded in
90 // the middle of the object, and it is going to be difficult to discover this until it is
91 // too late. It is recommended that throwExceptionOnFail == false be used primarily
92 // in simple cases where the allocation block is going to be used to build a small set
93 // of fixed-sized objects that one is sure are going to fit in the block.
94 //
95 void makeObjectAllocatorBlock(size_t numBytesIn, bool throwExceptionOnFail);
96 
97 // This is just like the above function, except that the allocation block is allocated
98 // by the programmer. As a result, it is NOT automatically freed once the count of
99 // the number of objects in the block goes to zero. After the next call to
100 // makeObjectAllocatorBlock () the Allocator will "forget" about the block completely.
101 // The caller is ultimately responsible for freeing the block.
102 void makeObjectAllocatorBlock(void* spaceToUse, size_t numBytesIn, bool throwExceptionOnFail);
103 
104 // this gets a count of the total number of free bytes available in the current
105 // allocation block.
107 
108 // this gets a count of the current number of individual, active objects that
109 // are present in the current allocation block
111 
112 
113 // this removes all references to all objects in the block containing forMe, and
114 // it deallocates the block if needed
115 void emptyOutContainingBlock(void* forMe);
116 
117 
118 // this gets a count of the current number of individual, active objects that
119 // are present in the allocation block that houses the object pointed to by
120 // the supplied handle. If the allocation block is not known to the Allocator
121 // (because the memory to the allocation block was supplied to the Allocator
122 // via a call to makeObjectAllocatorBlock (void *, size_t, bool) and there
123 // has been a subsequent call to makeObjectAllocatorBlock () that caused the
124 // allocator to frget that particular block) then the result of the call is
125 // a zero. The only other way that the result of the call can be a zero is
126 // if the Handle forMe is equl to a nullptr.
127 template <class ObjType>
129 
130 // like the above, except that it finds the number of objects in the block
131 // that contains the specified pointer
132 unsigned getNumObjectsInAllocatorBlock(void* forMe);
133 
134 // creates and returns a RefCountedObject that is located in the current
135 // allocation block. This RefCountedObject can be converted into a handle
136 // via an assignment to a Handle <ObjType>. This function is used to get a handle
137 // to an existing object that may have been allocated (for example) on the stack.
138 // For example, we might use:
139 //
140 // class Supervisor : public Object {...}
141 //
142 // Handle <Supervisor> mySup;
143 // Supervisor temp (134, 124.5, myEmp); // allocate temp on the stack
144 // mySup = getHandle <Supervisor> (temp);
145 //
146 // Note that, just like the rest of these operations, you can use getHandle ()
147 // and not worry about where the various objects are allocated. They will be
148 // copied and moved around as needed. So you can allocate an object on the stack,
149 // get a Handle to it (as above) and then later assign that Handle to a handle
150 // that is located in an object Obj in the current allocation block... in this
151 // case, a deep copy will be performed, as needed, so that all handles in Obj
152 // will point only to Objects located in the current allocation block.
153 //
154 template <class ObjType>
155 RefCountedObject<ObjType>* getHandle(ObjType& forMe);
156 
157 // makes an object, allocating it in the current allocation block,
158 // and returns a handle to it. On failure (not enough RAM) the resulting
159 // handle == nullptr if the allocator is set up not to throw an exception,
160 // and an exception is thrown if it is set up this way.
161 //
162 // Example usage:
163 //
164 // class Supervisor : public Object {...}
165 //
166 // Handle <Object> mySup = makeObject <Supervisor> (134, 124.5, myEmp);
167 //
168 // Note that it is fine to assign an object create as type A to a Handle<B>
169 // as long as A is a subclass of B.
170 //
171 template <class ObjType, class... Args>
172 RefCountedObject<ObjType>* makeObject(Args&&... args);
173 
174 // This is just like the above function, but here the first param to
175 // makeObjectWithExtraStorage is the number of bytes to pad the object
176 // with at the end. This is useful when you want additional storage at
177 // the end of the object, but it is not clear at compile time how much
178 // you will need... it is used, for example, to implement the Vector
179 // class where we want an Object with a bunch of extra storage at the
180 // end that stores the Vector's data
181 template <class ObjType, class... Args>
182 RefCountedObject<ObjType>* makeObjectWithExtraStorage(size_t extra, Args&&... args);
183 
184 // This gets a raw, bytewise representation of an object from a Handle. This
185 // call is always executed in constant time, so it is fast. The resulting bytes
186 // are then easily moved around. For example, consider the following code:
187 //
188 // /* Create an object */
189 //
190 // Handle <Supervisor> foo = makeObject <Supervisor> (134, 124.5, myEmp);
191 //
192 // /* Use getRecord to get the raw bytes of the object */
193 //
194 // Record <Supervisor> *myBytes = getRecord <Supervisor> (foo);
195 //
196 // /* Write the object to a file */
197 //
198 // int filedesc = open ("testfile", O_WRONLY | O_APPEND);
199 // write (filedesc, myBytes, myBytes->numBytes);
200 // close (filedesc);
201 //
202 // /* Then, at a later time, get the object back */
203 //
204 // filedesc = open ("testfile", O_RDONLY);
205 // Record <Supervisor> *myNewBytes = (Record <Supervisor> *) malloc (myBytes->numBytes);
206 // read (filedesc, myNewBytes, myBytes->numBytes);
207 // Handle <Supervisor> bar = myNewBytes-> getRootObject ();
208 //
209 // A nullptr is returned on an error.
210 //
211 // While this is super-fast (the call to getRecord runs in constant
212 // time, regardless of the size of the underlying object), there are
213 // three important caveats:
214 //
215 // (1) The callee does not own the resulting Record <ObjType> *.
216 // It will be deallocated automatically. In particular,
217 // if the destructor for Handle <ObjType> &forMe is called,
218 // or forMe is assigned to new value at a later time,
219 // then the resulting Record <ObjType> *can be made invalid.
220 // Also, a second call to getRecord () can make the resulting
221 // Record <ObjType> * invalid. However, as long as forMe
222 // is not destructed or modified, the resulting
223 // Record <ObjType> * will be valid.
224 //
225 // (2) There can be a lot of garbage inside of the resulting
226 // Record <ObjType> *. In particular, everything that was
227 // allocated using the same allocator as forMe will also be
228 // contained in the result of the call. So while this is fast,
229 // it can be space-inefficient.
230 //
231 // (3) This method will fail (return a null pointer) if the object
232 // pointed to by forMe is not housed in an allocation block
233 // managed by the Allocator associated with this thread.
234 //
235 template <class ObjType>
237 
238 // This is like Record <ObjType> *getRecord (Handle <ObjType> &forMe)
239 // except that the bytes for the object pointed to by forMe are actually
240 // copied to the space pointed to by putMeHere. On a failure (because
241 // there was not enough space available to hold a copy of the object)
242 // an exception is thrown.
243 //
244 // The cost of using this version of getRecord as opposed to the previous
245 // version is that a deep copy is required, which can be expensive. The
246 // benefit is that the resulting Record representation can be much more
247 // compact, because there are going to be no other objects mixed in with
248 // the bytes. Further, this not fail simply because the object pointed to
249 // by forMe is not located in an allocation block managed by this thread's
250 // allocator.
251 //
252 template <class ObjType>
253 Record<ObjType>* getRecord(Handle<ObjType>& forMe, void* putMeHere, size_t numBytesAvailable);
254 
255 // this gets the type ID that the system has assigned to this particular type
256 template <class ObjType>
257 int16_t getTypeID();
258 
259 // this performs a cast whose safety is not verifiable at compile time---note
260 // that because of difficulties stemming from the use of shared libraries,
261 // it is not possible to verify the correctness of the cast at runtime, either.
262 // So use this operation CAREFULLY!!
263 template <class OutObjType, class InObjType>
265 /*
266 template <class TargetType>
267 class Holder : public Object{
268  public:
269  Handle<TargetType> child;
270  ENABLE_DEEP_COPY
271 };
272 */
273 
274 // To facilitate deep copy, even in case copyMe is of an abstract class
275 // added by Jia based on Chris' proposal
276 template <class TargetType>
278 }
279 
280 #include "InterfaceFunctions.cc"
281 
282 #endif
RefCountedObject< ObjType > * getHandle(ObjType &forMe)
size_t getBytesAvailableInCurrentAllocatorBlock()
Handle< TargetType > deepCopyToCurrentAllocationBlock(Handle< TargetType > &copyMe)
RefCountedObject< ObjType > * makeObjectWithExtraStorage(size_t extra, Args &&...args)
Record< ObjType > * getRecord(Handle< ObjType > &forMe)
unsigned getNumObjectsInHomeAllocatorBlock(Handle< ObjType > &forMe)
int16_t getTypeID()
RefCountedObject< ObjType > * makeObject(Args &&...args)
unsigned getNumObjectsInAllocatorBlock(void *forMe)
Handle< OutObjType > unsafeCast(Handle< InObjType > &castMe)
unsigned getNumObjectsInCurrentAllocatorBlock()
void makeObjectAllocatorBlock(size_t numBytesIn, bool throwExceptionOnFail)
void emptyOutContainingBlock(void *forMe)