Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
C
capnproto
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
capnproto
Commits
68733fb7
Commit
68733fb7
authored
Jul 22, 2013
by
Kenton Varda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Thread-safe message builders... not performing well. Might revert.
parent
64075f16
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
197 additions
and
75 deletions
+197
-75
arena.c++
c++/src/capnp/arena.c++
+43
-25
arena.h
c++/src/capnp/arena.h
+44
-21
dynamic.c++
c++/src/capnp/dynamic.c++
+2
-2
dynamic.h
c++/src/capnp/dynamic.h
+2
-2
encoding-test.c++
c++/src/capnp/encoding-test.c++
+49
-0
layout-test.c++
c++/src/capnp/layout-test.c++
+9
-6
layout.c++
c++/src/capnp/layout.c++
+0
-0
message.c++
c++/src/capnp/message.c++
+8
-10
message.h
c++/src/capnp/message.h
+32
-1
orphan.h
c++/src/capnp/orphan.h
+8
-8
No files found.
c++/src/capnp/arena.c++
View file @
68733fb7
...
...
@@ -38,8 +38,9 @@ void ReadLimiter::unread(WordCount64 amount) {
// Be careful not to overflow here. Since ReadLimiter has no thread-safety, it's possible that
// the limit value was not updated correctly for one or more reads, and therefore unread() could
// overflow it even if it is only unreading bytes that were acutally read.
WordCount64
newValue
=
limit
+
amount
;
if
(
newValue
>
limit
)
{
uint64_t
oldValue
=
limit
;
uint64_t
newValue
=
oldValue
+
amount
/
WORDS
;
if
(
newValue
>
oldValue
)
{
limit
=
newValue
;
}
}
...
...
@@ -109,52 +110,63 @@ SegmentBuilder* BuilderArena::getSegment(SegmentId id) {
// This method is allowed to fail if the segment ID is not valid.
if
(
id
==
SegmentId
(
0
))
{
return
&
segment0
;
}
else
KJ_IF_MAYBE
(
s
,
moreSegments
)
{
KJ_REQUIRE
(
id
.
value
-
1
<
s
->
builders
.
size
(),
"invalid segment id"
,
id
.
value
);
return
s
->
builders
[
id
.
value
-
1
].
get
();
}
else
{
KJ_FAIL_REQUIRE
(
"invalid segment id"
,
id
.
value
);
auto
lock
=
moreSegments
.
lockShared
();
KJ_IF_MAYBE
(
s
,
*
lock
)
{
KJ_REQUIRE
(
id
.
value
-
1
<
s
->
builders
.
size
(),
"invalid segment id"
,
id
.
value
);
// TODO(cleanup): Return a const SegmentBuilder and tediously constify all SegmentBuilder
// pointers throughout the codebase.
return
const_cast
<
SegmentBuilder
*>
(
s
->
builders
[
id
.
value
-
1
].
get
());
}
else
{
KJ_FAIL_REQUIRE
(
"invalid segment id"
,
id
.
value
);
}
}
}
SegmentBuilder
*
BuilderArena
::
getSegmentWithAvailable
(
WordCount
minimumAvailable
)
{
// TODO(someday): Mutex-locking? Do we want to allow people to build different parts of the
// same message in different threads?
BuilderArena
::
AllocateResult
BuilderArena
::
allocate
(
WordCount
amount
)
{
if
(
segment0
.
getArena
()
==
nullptr
)
{
// We're allocating the first segment.
kj
::
ArrayPtr
<
word
>
ptr
=
message
->
allocateSegment
(
minimumAvailable
/
WORDS
);
// We're allocating the first segment. We don't need to worry about threads at this point
// because calling MessageBuilder::initRoot() from multiple threads is not intended to be safe.
kj
::
ArrayPtr
<
word
>
ptr
=
message
->
allocateSegment
(
amount
/
WORDS
);
// Re-allocate segment0 in-place. This is a bit of a hack, but we have not returned any
// pointers to this segment yet, so it should be fine.
segment0
.
~
SegmentBuilder
();
return
new
(
&
segment0
)
SegmentBuilder
(
this
,
SegmentId
(
0
),
ptr
,
&
this
->
dummyLimiter
);
kj
::
dtor
(
segment0
);
kj
::
ctor
(
segment0
,
this
,
SegmentId
(
0
),
ptr
,
&
this
->
dummyLimiter
);
return
AllocateResult
{
&
segment0
,
segment0
.
allocate
(
amount
)
};
}
else
{
if
(
segment0
.
available
()
>=
minimumAvailable
)
{
return
&
segment0
;
// Check if there is space in the first segment. We can do this without locking.
word
*
attempt
=
segment0
.
allocate
(
amount
);
if
(
attempt
!=
nullptr
)
{
return
AllocateResult
{
&
segment0
,
attempt
};
}
// Need to fall back to additional segments.
auto
lock
=
moreSegments
.
lockExclusive
();
MultiSegmentState
*
segmentState
;
KJ_IF_MAYBE
(
s
,
moreSegments
)
{
KJ_IF_MAYBE
(
s
,
*
lock
)
{
// TODO(perf): Check for available space in more than just the last segment. We don't
// want this to be O(n), though, so we'll need to maintain some sort of table. Complicating
// matters, we want SegmentBuilders::allocate() to be fast, so we can't update any such
// table when allocation actually happens. Instead, we could have a priority queue based
// on the last-known available size, and then re-check the size when we pop segments off it
// and shove them to the back of the queue if they have become too small.
if
(
s
->
builders
.
back
()
->
available
()
>=
minimumAvailable
)
{
return
s
->
builders
.
back
().
get
();
attempt
=
s
->
builders
.
back
()
->
allocate
(
amount
);
if
(
attempt
!=
nullptr
)
{
return
AllocateResult
{
s
->
builders
.
back
().
get
(),
attempt
};
}
segmentState
=
s
;
}
else
{
auto
newSegmentState
=
kj
::
heap
<
MultiSegmentState
>
();
segmentState
=
newSegmentState
;
moreSegments
=
kj
::
mv
(
newSegmentState
);
*
lock
=
kj
::
mv
(
newSegmentState
);
}
kj
::
Own
<
SegmentBuilder
>
newBuilder
=
kj
::
heap
<
SegmentBuilder
>
(
this
,
SegmentId
(
segmentState
->
builders
.
size
()
+
1
),
message
->
allocateSegment
(
minimumAvailable
/
WORDS
),
&
this
->
dummyLimiter
);
message
->
allocateSegment
(
amount
/
WORDS
),
&
this
->
dummyLimiter
);
SegmentBuilder
*
result
=
newBuilder
.
get
();
segmentState
->
builders
.
push_back
(
kj
::
mv
(
newBuilder
));
...
...
@@ -162,7 +174,9 @@ SegmentBuilder* BuilderArena::getSegmentWithAvailable(WordCount minimumAvailable
// getSegmentsForOutput(), which callers might reasonably expect is a thread-safe method.
segmentState
->
forOutput
.
resize
(
segmentState
->
builders
.
size
()
+
1
);
return
result
;
// Allocating from the new segment is guaranteed to succeed since no other thread could have
// received a pointer to it yet (since we still hold the lock).
return
AllocateResult
{
result
,
result
->
allocate
(
amount
)
};
}
}
...
...
@@ -172,7 +186,7 @@ kj::ArrayPtr<const kj::ArrayPtr<const word>> BuilderArena::getSegmentsForOutput(
// segments is actually changing due to an activity in another thread, then the caller has a
// problem regardless of locking here.
KJ_IF_MAYBE
(
segmentState
,
moreSegments
)
{
KJ_IF_MAYBE
(
segmentState
,
moreSegments
.
getWithoutLock
()
)
{
KJ_DASSERT
(
segmentState
->
forOutput
.
size
()
==
segmentState
->
builders
.
size
()
+
1
,
"segmentState->forOutput wasn't resized correctly when the last builder was added."
,
segmentState
->
forOutput
.
size
(),
segmentState
->
builders
.
size
());
...
...
@@ -206,9 +220,13 @@ SegmentReader* BuilderArena::tryGetSegment(SegmentId id) {
return
&
segment0
;
}
}
else
{
KJ_IF_MAYBE
(
segmentState
,
moreSegments
)
{
auto
lock
=
moreSegments
.
lockShared
();
KJ_IF_MAYBE
(
segmentState
,
*
lock
)
{
if
(
id
.
value
<=
segmentState
->
builders
.
size
())
{
return
segmentState
->
builders
[
id
.
value
-
1
].
get
();
// TODO(cleanup): Return a const SegmentReader and tediously constify all SegmentBuilder
// pointers throughout the codebase.
return
const_cast
<
SegmentReader
*>
(
kj
::
implicitCast
<
const
SegmentReader
*>
(
segmentState
->
builders
[
id
.
value
-
1
].
get
()));
}
}
return
nullptr
;
...
...
c++/src/capnp/arena.h
View file @
68733fb7
...
...
@@ -31,6 +31,7 @@
#include <vector>
#include <unordered_map>
#include <kj/common.h>
#include <kj/mutex.h>
#include "common.h"
#include "message.h"
...
...
@@ -61,6 +62,10 @@ class ReadLimiter {
// readers. If you call the same getter twice, the data it returns may be double-counted. This
// should not be a big deal in most cases -- just set the read limit high enough that it will
// only trigger in unreasonable cases.
//
// This class is "safe" to use from multiple threads for its intended use case. Threads may
// overwrite each others' changes to the counter, but this is OK because it only means that the
// limit is enforced a bit less strictly -- it will still kick in eventually.
public
:
inline
explicit
ReadLimiter
();
// No limit.
...
...
@@ -75,7 +80,10 @@ public:
// some data.
private
:
WordCount64
limit
;
volatile
uint64_t
limit
;
// Current limit, decremented each time catRead() is called. Volatile because multiple threads
// could be trying to modify it at once. (This is not real thread-safety, but good enough for
// the purpose of this class. See class comment.)
KJ_DISALLOW_COPY
(
ReadLimiter
);
};
...
...
@@ -120,8 +128,6 @@ public:
inline
BuilderArena
*
getArena
();
inline
WordCount
available
();
inline
kj
::
ArrayPtr
<
const
word
>
currentlyAllocated
();
inline
void
reset
();
...
...
@@ -174,12 +180,21 @@ public:
~
BuilderArena
()
noexcept
(
false
);
KJ_DISALLOW_COPY
(
BuilderArena
);
inline
SegmentBuilder
*
getRootSegment
()
{
return
&
segment0
;
}
SegmentBuilder
*
getSegment
(
SegmentId
id
);
// Get the segment with the given id. Crashes or throws an exception if no such segment exists.
SegmentBuilder
*
getSegmentWithAvailable
(
WordCount
minimumAvailable
);
// Get a segment which has at least the given amount of space available, allocating it if
// necessary. Crashes or throws an exception if there is not enough memory.
struct
AllocateResult
{
SegmentBuilder
*
segment
;
word
*
words
;
};
AllocateResult
allocate
(
WordCount
amount
);
// Find a segment with at least the given amount of space available and allocate the space.
// Note that allocating directly from a particular segment is much faster, but allocating from
// the arena is guaranteed to succeed. Therefore callers should try to allocate from a specific
// segment first if there is one, then fall back to the arena.
kj
::
ArrayPtr
<
const
kj
::
ArrayPtr
<
const
word
>>
getSegmentsForOutput
();
// Get an array of all the segments, suitable for writing out. This only returns the allocated
...
...
@@ -203,25 +218,28 @@ private:
std
::
vector
<
kj
::
Own
<
SegmentBuilder
>>
builders
;
std
::
vector
<
kj
::
ArrayPtr
<
const
word
>>
forOutput
;
};
kj
::
M
aybe
<
kj
::
Own
<
MultiSegmentState
>>
moreSegments
;
kj
::
M
utexGuarded
<
kj
::
Maybe
<
kj
::
Own
<
MultiSegmentState
>
>>
moreSegments
;
};
// =======================================================================================
inline
ReadLimiter
::
ReadLimiter
()
// I didn't want to #include <limits> just for this one lousy constant.
:
limit
(
uint64_t
(
0x7fffffffffffffffll
)
*
WORDS
)
{}
:
limit
(
0x7fffffffffffffffllu
)
{}
inline
ReadLimiter
::
ReadLimiter
(
WordCount64
limit
)
:
limit
(
limit
)
{}
inline
ReadLimiter
::
ReadLimiter
(
WordCount64
limit
)
:
limit
(
limit
/
WORDS
)
{}
inline
void
ReadLimiter
::
reset
(
WordCount64
limit
)
{
this
->
limit
=
limit
;
}
inline
void
ReadLimiter
::
reset
(
WordCount64
limit
)
{
this
->
limit
=
limit
/
WORDS
;
}
inline
bool
ReadLimiter
::
canRead
(
WordCount
amount
,
Arena
*
arena
)
{
if
(
KJ_UNLIKELY
(
amount
>
limit
))
{
// Be careful not to store an underflowed value into `limit`, even if multiple threads are
// decrementing it.
uint64_t
current
=
limit
;
if
(
KJ_UNLIKELY
(
amount
/
WORDS
>
current
))
{
arena
->
reportReadLimitReached
();
return
false
;
}
else
{
limit
-=
amount
;
limit
=
current
-
amount
/
WORDS
;
return
true
;
}
}
...
...
@@ -258,13 +276,22 @@ inline SegmentBuilder::SegmentBuilder(
pos
(
ptr
.
begin
())
{}
inline
word
*
SegmentBuilder
::
allocate
(
WordCount
amount
)
{
if
(
amount
>
intervalLength
(
pos
,
ptr
.
end
()))
{
word
*
result
=
__atomic_fetch_add
(
&
pos
,
amount
*
BYTES_PER_WORD
/
BYTES
,
__ATOMIC_RELAXED
);
// Careful about pointer arithmetic here. The segment might be at the end of the address space,
// or `amount` could be ridiculously huge.
if
(
ptr
.
end
()
-
(
result
+
amount
)
<
0
)
{
// Not enough space in the segment for this allocation.
if
(
ptr
.
end
()
-
result
>=
0
)
{
// It was our increment that pushed the pointer past the end of the segment. Therefore no
// other thread could have accidentally allocated space in this segment in the meantime.
// We need to back up the pointer so that it will be correct when the data is written out
// (and also so that another allocation can potentially use the remaining space).
__atomic_store_n
(
&
pos
,
result
,
__ATOMIC_RELAXED
);
}
return
nullptr
;
}
else
{
// TODO(someday): Atomic increment, backtracking if we go over, would make this thread-safe.
// How much would it cost in the single-threaded case? Is it free? Benchmark it.
word
*
result
=
pos
;
pos
+=
amount
;
// Success.
return
result
;
}
}
...
...
@@ -281,10 +308,6 @@ inline BuilderArena* SegmentBuilder::getArena() {
return
static_cast
<
BuilderArena
*>
(
arena
);
}
inline
WordCount
SegmentBuilder
::
available
()
{
return
intervalLength
(
pos
,
ptr
.
end
());
}
inline
kj
::
ArrayPtr
<
const
word
>
SegmentBuilder
::
currentlyAllocated
()
{
return
kj
::
arrayPtr
(
ptr
.
begin
(),
pos
-
ptr
.
begin
());
}
...
...
c++/src/capnp/dynamic.c++
View file @
68733fb7
...
...
@@ -1559,12 +1559,12 @@ DynamicList::Builder PointerHelpers<DynamicList, Kind::UNKNOWN>::init(
// -------------------------------------------------------------------
Orphan
<
DynamicStruct
>
Orphanage
::
newOrphan
(
StructSchema
schema
)
{
Orphan
<
DynamicStruct
>
Orphanage
::
newOrphan
(
StructSchema
schema
)
const
{
return
Orphan
<
DynamicStruct
>
(
schema
,
_
::
OrphanBuilder
::
initStruct
(
arena
,
structSizeFromSchema
(
schema
)));
}
Orphan
<
DynamicList
>
Orphanage
::
newOrphan
(
ListSchema
schema
,
uint
size
)
{
Orphan
<
DynamicList
>
Orphanage
::
newOrphan
(
ListSchema
schema
,
uint
size
)
const
{
if
(
schema
.
whichElementType
()
==
schema
::
Type
::
Body
::
STRUCT_TYPE
)
{
return
Orphan
<
DynamicList
>
(
schema
,
_
::
OrphanBuilder
::
initStructList
(
arena
,
size
*
ELEMENTS
,
structSizeFromSchema
(
schema
.
getStructElementType
())));
...
...
c++/src/capnp/dynamic.h
View file @
68733fb7
...
...
@@ -748,14 +748,14 @@ struct Orphanage::GetInnerBuilder<DynamicList, Kind::UNKNOWN> {
template
<>
inline
Orphan
<
DynamicStruct
>
Orphanage
::
newOrphanCopy
<
DynamicStruct
::
Reader
>
(
const
DynamicStruct
::
Reader
&
copyFrom
)
{
const
DynamicStruct
::
Reader
&
copyFrom
)
const
{
return
Orphan
<
DynamicStruct
>
(
copyFrom
.
getSchema
(),
_
::
OrphanBuilder
::
copy
(
arena
,
copyFrom
.
reader
));
}
template
<>
inline
Orphan
<
DynamicList
>
Orphanage
::
newOrphanCopy
<
DynamicList
::
Reader
>
(
const
DynamicList
::
Reader
&
copyFrom
)
{
const
DynamicList
::
Reader
&
copyFrom
)
const
{
return
Orphan
<
DynamicList
>
(
copyFrom
.
getSchema
(),
_
::
OrphanBuilder
::
copy
(
arena
,
copyFrom
.
reader
));
}
...
...
c++/src/capnp/encoding-test.c++
View file @
68733fb7
...
...
@@ -23,6 +23,7 @@
#include <capnp/test-import.capnp.h>
#include "message.h"
#include <kj/thread.h>
#include <kj/debug.h>
#include <gtest/gtest.h>
#include "test-util.h"
...
...
@@ -1350,6 +1351,54 @@ TEST(Encoding, Has) {
EXPECT_TRUE
(
root
.
asReader
().
hasInt32List
());
}
TEST
(
Encoding
,
Threads
)
{
// Use fixed-size segments so that many segments are allocated during the test.
MallocMessageBuilder
message
(
1024
,
AllocationStrategy
::
FIXED_SIZE
);
kj
::
MutexGuarded
<
Orphanage
>
orphanage
(
message
.
getOrphanage
());
auto
outerLock
=
orphanage
.
lockExclusive
();
auto
threadFunc
=
[
&
]()
{
int
dummy
;
uint64_t
me
=
reinterpret_cast
<
uintptr_t
>
(
&
dummy
);
{
// Make sure all threads start at the same time.
auto
lock
=
orphanage
.
lockShared
();
// Allocate space for a list. This will always end up allocating a new segment.
auto
list
=
lock
->
newOrphan
<
List
<
List
<
uint64_t
>>>
(
10000
);
auto
builder
=
list
.
get
();
// Allocate a bunch of smaller lists and initialize them to values specific to this thread.
for
(
uint
i
=
0
;
i
<
builder
.
size
();
i
++
)
{
builder
.
set
(
i
,
{
me
,
me
+
1
,
me
+
2
,
me
+
3
});
}
// Check that none of the values were corrupted.
for
(
auto
item
:
list
.
getReader
())
{
ASSERT_EQ
(
4
,
item
.
size
());
EXPECT_EQ
(
me
,
item
[
0
]);
EXPECT_EQ
(
me
+
1
,
item
[
1
]);
EXPECT_EQ
(
me
+
2
,
item
[
2
]);
EXPECT_EQ
(
me
+
3
,
item
[
3
]);
}
}
};
kj
::
Thread
thread1
(
threadFunc
);
kj
::
Thread
thread2
(
threadFunc
);
kj
::
Thread
thread3
(
threadFunc
);
kj
::
Thread
thread4
(
threadFunc
);
usleep
(
10000
);
auto
releaseLock
=
kj
::
mv
(
outerLock
);
// On the way out, we'll release the lock, thus allowing the threads to start, then we'll join
// each thread, thus waiting for them all to complete.
}
}
// namespace
}
// namespace _ (private)
}
// namespace capnp
c++/src/capnp/layout-test.c++
View file @
68733fb7
...
...
@@ -271,8 +271,9 @@ static void checkStruct(StructReader reader) {
TEST
(
WireFormat
,
StructRoundTrip_OneSegment
)
{
MallocMessageBuilder
message
;
BuilderArena
arena
(
&
message
);
SegmentBuilder
*
segment
=
arena
.
getSegmentWithAvailable
(
1
*
WORDS
);
word
*
rootLocation
=
segment
->
allocate
(
1
*
WORDS
);
auto
allocation
=
arena
.
allocate
(
1
*
WORDS
);
SegmentBuilder
*
segment
=
allocation
.
segment
;
word
*
rootLocation
=
allocation
.
words
;
StructBuilder
builder
=
StructBuilder
::
initRoot
(
segment
,
rootLocation
,
StructSize
(
2
*
WORDS
,
4
*
POINTERS
,
FieldSize
::
INLINE_COMPOSITE
));
...
...
@@ -307,8 +308,9 @@ TEST(WireFormat, StructRoundTrip_OneSegment) {
TEST
(
WireFormat
,
StructRoundTrip_OneSegmentPerAllocation
)
{
MallocMessageBuilder
message
(
0
,
AllocationStrategy
::
FIXED_SIZE
);
BuilderArena
arena
(
&
message
);
SegmentBuilder
*
segment
=
arena
.
getSegmentWithAvailable
(
1
*
WORDS
);
word
*
rootLocation
=
segment
->
allocate
(
1
*
WORDS
);
auto
allocation
=
arena
.
allocate
(
1
*
WORDS
);
SegmentBuilder
*
segment
=
allocation
.
segment
;
word
*
rootLocation
=
allocation
.
words
;
StructBuilder
builder
=
StructBuilder
::
initRoot
(
segment
,
rootLocation
,
StructSize
(
2
*
WORDS
,
4
*
POINTERS
,
FieldSize
::
INLINE_COMPOSITE
));
...
...
@@ -344,8 +346,9 @@ TEST(WireFormat, StructRoundTrip_OneSegmentPerAllocation) {
TEST
(
WireFormat
,
StructRoundTrip_MultipleSegmentsWithMultipleAllocations
)
{
MallocMessageBuilder
message
(
8
,
AllocationStrategy
::
FIXED_SIZE
);
BuilderArena
arena
(
&
message
);
SegmentBuilder
*
segment
=
arena
.
getSegmentWithAvailable
(
1
*
WORDS
);
word
*
rootLocation
=
segment
->
allocate
(
1
*
WORDS
);
auto
allocation
=
arena
.
allocate
(
1
*
WORDS
);
SegmentBuilder
*
segment
=
allocation
.
segment
;
word
*
rootLocation
=
allocation
.
words
;
StructBuilder
builder
=
StructBuilder
::
initRoot
(
segment
,
rootLocation
,
StructSize
(
2
*
WORDS
,
4
*
POINTERS
,
FieldSize
::
INLINE_COMPOSITE
));
...
...
c++/src/capnp/layout.c++
View file @
68733fb7
This diff is collapsed.
Click to expand it.
c++/src/capnp/message.c++
View file @
68733fb7
...
...
@@ -65,7 +65,7 @@ _::StructReader MessageReader::getRootInternal() {
MessageBuilder
::
MessageBuilder
()
:
allocatedArena
(
false
)
{}
MessageBuilder
::~
MessageBuilder
()
noexcept
(
false
)
{
if
(
allocatedArena
)
{
arena
()
->~
BuilderArena
(
);
kj
::
dtor
(
*
arena
()
);
}
}
...
...
@@ -74,19 +74,17 @@ _::SegmentBuilder* MessageBuilder::getRootSegment() {
return
arena
()
->
getSegment
(
_
::
SegmentId
(
0
));
}
else
{
static_assert
(
sizeof
(
_
::
BuilderArena
)
<=
sizeof
(
arenaSpace
),
"arenaSpace is too small to hold a BuilderArena. Please increase it. This will break "
"ABI compatibility."
);
new
(
arena
())
_
::
BuilderArena
(
this
);
"arenaSpace is too small to hold a BuilderArena. Please increase it."
);
kj
::
ctor
(
*
arena
(),
this
);
allocatedArena
=
true
;
WordCount
ptrSize
=
1
*
POINTERS
*
WORDS_PER_POINTER
;
_
::
SegmentBuilder
*
segment
=
arena
()
->
getSegmentWithAvailable
(
ptrSize
);
KJ_ASSERT
(
segment
->
getSegmentId
()
==
_
::
SegmentId
(
0
),
auto
allocation
=
arena
()
->
allocate
(
POINTER_SIZE_IN_WORDS
)
;
KJ_ASSERT
(
allocation
.
segment
->
getSegmentId
()
==
_
::
SegmentId
(
0
),
"First allocated word of new arena was not in segment ID 0."
);
word
*
location
=
segment
->
allocate
(
ptrSize
);
KJ_ASSERT
(
location
==
segment
->
getPtrUnchecked
(
0
*
WORDS
),
KJ_ASSERT
(
allocation
.
words
==
allocation
.
segment
->
getPtrUnchecked
(
0
*
WORDS
),
"First allocated word of new arena was not the first word in its segment."
);
return
segment
;
return
allocation
.
segment
;
}
}
...
...
c++/src/capnp/message.h
View file @
68733fb7
...
...
@@ -23,6 +23,7 @@
#include <kj/common.h>
#include <kj/memory.h>
#include <kj/mutex.h>
#include "common.h"
#include "layout.h"
...
...
@@ -78,6 +79,16 @@ struct ReaderOptions {
};
class
MessageReader
{
// Abstract interface for an object used to read a Cap'n Proto message. Subclasses of
// MessageReader are responsible for reading the raw, flat message content. Callers should
// usually call `messageReader.getRoot<MyStructType>()` to get a `MyStructType::Reader`
// representing the root of the message, then use that to traverse the message content.
//
// Some common subclasses of `MessageReader` include `SegmentArrayMessageReader`, whose
// constructor accepts pointers to the raw data, and `StreamFdMessageReader` (from
// `serialize.h`), which reads the message from a file descriptor. One might implement other
// subclasses to handle things like reading from shared memory segments, mmap()ed files, etc.
public
:
MessageReader
(
ReaderOptions
options
);
// It is suggested that subclasses take ReaderOptions as a constructor parameter, but give it a
...
...
@@ -120,6 +131,17 @@ private:
};
class
MessageBuilder
{
// Abstract interface for an object used to allocate and build a message. Subclasses of
// MessageBuilder are responsible for allocating the space in which the message will be written.
// The most common subclass is `MallocMessageBuilder`, but other subclasses may be used to do
// tricky things like allocate messages in shared memory or mmap()ed files.
//
// Creating a new message ususually means allocating a new MessageBuilder (ideally on the stack)
// and then calling `messageBuilder.initRoot<MyStructType>()` to get a `MyStructType::Builder`.
// That, in turn, can be used to fill in the message content. When done, you can call
// `messageBuilder.getSegmentsForOutput()` to get a list of flat data arrays containing the
// message.
public
:
MessageBuilder
();
virtual
~
MessageBuilder
()
noexcept
(
false
);
...
...
@@ -129,6 +151,9 @@ public:
// this is not possible. It is expected that this method will usually return more space than
// requested, and the caller should use that extra space as much as possible before allocating
// more. The returned space remains valid at least until the MessageBuilder is destroyed.
//
// Cap'n Proto will only call this once at a time, so the subclass need not worry about
// thread-safety.
template
<
typename
RootType
>
typename
RootType
::
Builder
initRoot
();
...
...
@@ -159,12 +184,18 @@ public:
Orphanage
getOrphanage
();
private
:
void
*
arenaSpace
[
15
+
sizeof
(
kj
::
MutexGuarded
<
void
*>
)
/
sizeof
(
void
*
)];
// Space in which we can construct a BuilderArena. We don't use BuilderArena directly here
// because we don't want clients to have to #include arena.h, which itself includes a bunch of
// big STL headers. We don't use a pointer to a BuilderArena because that would require an
// extra malloc on every message which could be expensive when processing small messages.
void
*
arenaSpace
[
15
];
bool
allocatedArena
=
false
;
// We have to initialize the arena lazily because when we do so we want to allocate the root
// pointer immediately, and this will allocate a segment, which requires a virtual function
// call on the MessageBuilder. We can't do such a call in the constructor since the subclass
// isn't constructed yet. This is kind of annoying because it means that getOrphanage() is
// not thread-safe, but that shouldn't be a huge deal...
_
::
BuilderArena
*
arena
()
{
return
reinterpret_cast
<
_
::
BuilderArena
*>
(
arenaSpace
);
}
_
::
SegmentBuilder
*
getRootSegment
();
...
...
c++/src/capnp/orphan.h
View file @
68733fb7
...
...
@@ -89,23 +89,23 @@ public:
// `getOrphanage()` method.
template
<
typename
RootType
>
Orphan
<
RootType
>
newOrphan
();
Orphan
<
RootType
>
newOrphan
()
const
;
// Allocate a new orphaned struct.
template
<
typename
RootType
>
Orphan
<
RootType
>
newOrphan
(
uint
size
);
Orphan
<
RootType
>
newOrphan
(
uint
size
)
const
;
// Allocate a new orphaned list or blob.
Orphan
<
DynamicStruct
>
newOrphan
(
StructSchema
schema
);
Orphan
<
DynamicStruct
>
newOrphan
(
StructSchema
schema
)
const
;
// Dynamically create an orphan struct with the given schema. You must
// #include <capnp/dynamic.h> to use this.
Orphan
<
DynamicList
>
newOrphan
(
ListSchema
schema
,
uint
size
);
Orphan
<
DynamicList
>
newOrphan
(
ListSchema
schema
,
uint
size
)
const
;
// Dynamically create an orphan list with the given schema. You must #include <capnp/dynamic.h>
// to use this.
template
<
typename
Reader
>
Orphan
<
FromReader
<
Reader
>>
newOrphanCopy
(
const
Reader
&
copyFrom
);
Orphan
<
FromReader
<
Reader
>>
newOrphanCopy
(
const
Reader
&
copyFrom
)
const
;
// Allocate a new orphaned object (struct, list, or blob) and initialize it as a copy of the
// given object.
...
...
@@ -214,7 +214,7 @@ Orphanage Orphanage::getForMessageContaining(BuilderType builder) {
}
template
<
typename
RootType
>
Orphan
<
RootType
>
Orphanage
::
newOrphan
()
{
Orphan
<
RootType
>
Orphanage
::
newOrphan
()
const
{
return
Orphan
<
RootType
>
(
_
::
OrphanBuilder
::
initStruct
(
arena
,
_
::
structSize
<
RootType
>
()));
}
...
...
@@ -247,7 +247,7 @@ struct Orphanage::NewOrphanListImpl<Data> {
};
template
<
typename
RootType
>
Orphan
<
RootType
>
Orphanage
::
newOrphan
(
uint
size
)
{
Orphan
<
RootType
>
Orphanage
::
newOrphan
(
uint
size
)
const
{
return
Orphan
<
RootType
>
(
NewOrphanListImpl
<
RootType
>::
apply
(
arena
,
size
));
}
...
...
@@ -273,7 +273,7 @@ struct Orphanage::GetInnerReader<T, Kind::BLOB> {
};
template
<
typename
Reader
>
Orphan
<
FromReader
<
Reader
>>
Orphanage
::
newOrphanCopy
(
const
Reader
&
copyFrom
)
{
Orphan
<
FromReader
<
Reader
>>
Orphanage
::
newOrphanCopy
(
const
Reader
&
copyFrom
)
const
{
return
Orphan
<
FromReader
<
Reader
>>
(
_
::
OrphanBuilder
::
copy
(
arena
,
GetInnerReader
<
FromReader
<
Reader
>>::
apply
(
copyFrom
)));
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment