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
fc0c297c
Commit
fc0c297c
authored
Jul 20, 2013
by
Kenton Varda
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make kj::Arena thread-safe.
parent
a45c463c
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
300 additions
and
117 deletions
+300
-117
lexer-test.c++
c++/src/capnp/compiler/lexer-test.c++
+1
-1
schema-loader.c++
c++/src/capnp/schema-loader.c++
+7
-7
arena-test.c++
c++/src/kj/arena-test.c++
+123
-9
arena.c++
c++/src/kj/arena.c++
+94
-44
arena.h
c++/src/kj/arena.h
+37
-27
mutex-test.c++
c++/src/kj/mutex-test.c++
+9
-9
mutex.c++
c++/src/kj/mutex.c++
+10
-7
mutex.h
c++/src/kj/mutex.h
+19
-13
No files found.
c++/src/capnp/compiler/lexer-test.c++
View file @
fc0c297c
...
@@ -31,7 +31,7 @@ namespace {
...
@@ -31,7 +31,7 @@ namespace {
class
TestFailingErrorReporter
:
public
ErrorReporter
{
class
TestFailingErrorReporter
:
public
ErrorReporter
{
public
:
public
:
void
addError
(
uint32_t
startByte
,
uint32_t
endByte
,
kj
::
String
message
)
override
{
void
addError
(
uint32_t
startByte
,
uint32_t
endByte
,
kj
::
String
Ptr
message
)
override
{
ADD_FAILURE
()
<<
"Parse failed: ("
<<
startByte
<<
"-"
<<
endByte
<<
") "
<<
message
.
cStr
();
ADD_FAILURE
()
<<
"Parse failed: ("
<<
startByte
<<
"-"
<<
endByte
<<
") "
<<
message
.
cStr
();
}
}
};
};
...
...
c++/src/capnp/schema-loader.c++
View file @
fc0c297c
...
@@ -1167,7 +1167,7 @@ void SchemaLoader::InitializerImpl::init(const _::RawSchema* schema) const {
...
@@ -1167,7 +1167,7 @@ void SchemaLoader::InitializerImpl::init(const _::RawSchema* schema) const {
// Lock the loader for read to make sure no one is concurrently loading a replacement for this
// Lock the loader for read to make sure no one is concurrently loading a replacement for this
// schema node.
// schema node.
auto
lock
=
loader
.
impl
.
lock
ForRea
d
();
auto
lock
=
loader
.
impl
.
lock
Share
d
();
// Get the mutable version of the schema.
// Get the mutable version of the schema.
_
::
RawSchema
*
mutableSchema
=
lock
->
get
()
->
tryGet
(
schema
->
id
).
schema
;
_
::
RawSchema
*
mutableSchema
=
lock
->
get
()
->
tryGet
(
schema
->
id
).
schema
;
...
@@ -1195,12 +1195,12 @@ Schema SchemaLoader::get(uint64_t id) const {
...
@@ -1195,12 +1195,12 @@ Schema SchemaLoader::get(uint64_t id) const {
}
}
kj
::
Maybe
<
Schema
>
SchemaLoader
::
tryGet
(
uint64_t
id
)
const
{
kj
::
Maybe
<
Schema
>
SchemaLoader
::
tryGet
(
uint64_t
id
)
const
{
auto
getResult
=
impl
.
lock
ForRea
d
()
->
get
()
->
tryGet
(
id
);
auto
getResult
=
impl
.
lock
Share
d
()
->
get
()
->
tryGet
(
id
);
if
(
getResult
.
schema
==
nullptr
||
getResult
.
schema
->
lazyInitializer
!=
nullptr
)
{
if
(
getResult
.
schema
==
nullptr
||
getResult
.
schema
->
lazyInitializer
!=
nullptr
)
{
KJ_IF_MAYBE
(
c
,
getResult
.
callback
)
{
KJ_IF_MAYBE
(
c
,
getResult
.
callback
)
{
c
->
load
(
*
this
,
id
);
c
->
load
(
*
this
,
id
);
}
}
getResult
=
impl
.
lock
ForRea
d
()
->
get
()
->
tryGet
(
id
);
getResult
=
impl
.
lock
Share
d
()
->
get
()
->
tryGet
(
id
);
}
}
if
(
getResult
.
schema
!=
nullptr
&&
getResult
.
schema
->
lazyInitializer
==
nullptr
)
{
if
(
getResult
.
schema
!=
nullptr
&&
getResult
.
schema
->
lazyInitializer
==
nullptr
)
{
return
Schema
(
getResult
.
schema
);
return
Schema
(
getResult
.
schema
);
...
@@ -1210,11 +1210,11 @@ kj::Maybe<Schema> SchemaLoader::tryGet(uint64_t id) const {
...
@@ -1210,11 +1210,11 @@ kj::Maybe<Schema> SchemaLoader::tryGet(uint64_t id) const {
}
}
Schema
SchemaLoader
::
load
(
const
schema
::
Node
::
Reader
&
reader
)
{
Schema
SchemaLoader
::
load
(
const
schema
::
Node
::
Reader
&
reader
)
{
return
Schema
(
impl
.
lock
()
->
get
()
->
load
(
reader
,
false
));
return
Schema
(
impl
.
lock
Exclusive
()
->
get
()
->
load
(
reader
,
false
));
}
}
Schema
SchemaLoader
::
loadOnce
(
const
schema
::
Node
::
Reader
&
reader
)
const
{
Schema
SchemaLoader
::
loadOnce
(
const
schema
::
Node
::
Reader
&
reader
)
const
{
auto
locked
=
impl
.
lock
();
auto
locked
=
impl
.
lock
Exclusive
();
auto
getResult
=
locked
->
get
()
->
tryGet
(
reader
.
getId
());
auto
getResult
=
locked
->
get
()
->
tryGet
(
reader
.
getId
());
if
(
getResult
.
schema
==
nullptr
||
getResult
.
schema
->
lazyInitializer
!=
nullptr
)
{
if
(
getResult
.
schema
==
nullptr
||
getResult
.
schema
->
lazyInitializer
!=
nullptr
)
{
// Doesn't exist yet, or the existing schema is a placeholder and therefore has not yet been
// Doesn't exist yet, or the existing schema is a placeholder and therefore has not yet been
...
@@ -1226,11 +1226,11 @@ Schema SchemaLoader::loadOnce(const schema::Node::Reader& reader) const {
...
@@ -1226,11 +1226,11 @@ Schema SchemaLoader::loadOnce(const schema::Node::Reader& reader) const {
}
}
kj
::
Array
<
Schema
>
SchemaLoader
::
getAllLoaded
()
const
{
kj
::
Array
<
Schema
>
SchemaLoader
::
getAllLoaded
()
const
{
return
impl
.
lock
ForRea
d
()
->
get
()
->
getAllLoaded
();
return
impl
.
lock
Share
d
()
->
get
()
->
getAllLoaded
();
}
}
void
SchemaLoader
::
loadNative
(
const
_
::
RawSchema
*
nativeSchema
)
{
void
SchemaLoader
::
loadNative
(
const
_
::
RawSchema
*
nativeSchema
)
{
impl
.
lock
()
->
get
()
->
loadNative
(
nativeSchema
);
impl
.
lock
Exclusive
()
->
get
()
->
loadNative
(
nativeSchema
);
}
}
}
// namespace capnp
}
// namespace capnp
c++/src/kj/arena-test.c++
View file @
fc0c297c
...
@@ -197,32 +197,50 @@ TEST(Arena, Alignment) {
...
@@ -197,32 +197,50 @@ TEST(Arena, Alignment) {
TEST
(
Arena
,
EndOfChunk
)
{
TEST
(
Arena
,
EndOfChunk
)
{
union
{
union
{
byte
scratch
[
16
];
byte
scratch
[
64
];
uint64_t
align
;
uint64_t
align
;
};
};
Arena
arena
(
arrayPtr
(
scratch
,
sizeof
(
scratch
)));
Arena
arena
(
arrayPtr
(
scratch
,
sizeof
(
scratch
)));
// First allocation will come from somewhere in the scratch space (after the chunk header).
uint64_t
&
i
=
arena
.
allocate
<
uint64_t
>
();
uint64_t
&
i
=
arena
.
allocate
<
uint64_t
>
();
EXPECT_EQ
(
scratch
,
reinterpret_cast
<
byte
*>
(
&
i
));
EXPECT_GE
(
reinterpret_cast
<
byte
*>
(
&
i
),
scratch
);
EXPECT_LT
(
reinterpret_cast
<
byte
*>
(
&
i
),
scratch
+
sizeof
(
scratch
));
// Next allocation will come at the next position.
uint64_t
&
i2
=
arena
.
allocate
<
uint64_t
>
();
uint64_t
&
i2
=
arena
.
allocate
<
uint64_t
>
();
EXPECT_EQ
(
scratch
+
8
,
reinterpret_cast
<
byte
*>
(
&
i2
)
);
EXPECT_EQ
(
&
i
+
1
,
&
i2
);
// Allocate the rest of the scratch space.
size_t
spaceLeft
=
scratch
+
sizeof
(
scratch
)
-
reinterpret_cast
<
byte
*>
(
&
i2
+
1
);
ArrayPtr
<
byte
>
remaining
=
arena
.
allocateArray
<
byte
>
(
spaceLeft
);
EXPECT_EQ
(
reinterpret_cast
<
byte
*>
(
&
i2
+
1
),
remaining
.
begin
());
// Next allocation comes from somewhere new.
uint64_t
&
i3
=
arena
.
allocate
<
uint64_t
>
();
uint64_t
&
i3
=
arena
.
allocate
<
uint64_t
>
();
EXPECT_NE
(
scratch
+
16
,
reinterpret_cast
<
byte
*>
(
&
i3
));
EXPECT_NE
(
remaining
.
end
()
,
reinterpret_cast
<
byte
*>
(
&
i3
));
}
}
TEST
(
Arena
,
EndOfChunkAlignment
)
{
TEST
(
Arena
,
EndOfChunkAlignment
)
{
union
{
union
{
byte
scratch
[
10
];
byte
scratch
[
34
];
uint64_t
align
;
uint64_t
align
;
};
};
Arena
arena
(
arrayPtr
(
scratch
,
sizeof
(
scratch
)));
Arena
arena
(
arrayPtr
(
scratch
,
sizeof
(
scratch
)));
// Figure out where we are...
byte
*
start
=
arena
.
allocateArray
<
byte
>
(
0
).
begin
();
// Allocate enough space so that we're 24 bytes into the scratch space. (On 64-bit systems, this
// should be zero.)
arena
.
allocateArray
<
byte
>
(
24
-
(
start
-
scratch
));
// Allocating a 16-bit integer works. Now we're at 26 bytes; 8 bytes are left.
uint16_t
&
i
=
arena
.
allocate
<
uint16_t
>
();
uint16_t
&
i
=
arena
.
allocate
<
uint16_t
>
();
EXPECT_EQ
(
scratch
,
reinterpret_cast
<
byte
*>
(
&
i
));
EXPECT_EQ
(
scratch
+
24
,
reinterpret_cast
<
byte
*>
(
&
i
));
// Although there is technically enough space in the scratch array, it is not aligned correctly.
// Although there is technically enough space to allocate a uint64, it is not aligned correctly,
// so it will be allocated elsewhere instead.
uint64_t
&
i2
=
arena
.
allocate
<
uint64_t
>
();
uint64_t
&
i2
=
arena
.
allocate
<
uint64_t
>
();
EXPECT_TRUE
(
reinterpret_cast
<
byte
*>
(
&
i2
)
<
scratch
||
EXPECT_TRUE
(
reinterpret_cast
<
byte
*>
(
&
i2
)
<
scratch
||
reinterpret_cast
<
byte
*>
(
&
i2
)
>
scratch
+
sizeof
(
scratch
));
reinterpret_cast
<
byte
*>
(
&
i2
)
>
scratch
+
sizeof
(
scratch
));
...
@@ -237,14 +255,19 @@ TEST(Arena, TooBig) {
...
@@ -237,14 +255,19 @@ TEST(Arena, TooBig) {
byte
&
b2
=
arena
.
allocate
<
byte
>
();
byte
&
b2
=
arena
.
allocate
<
byte
>
();
EXPECT_EQ
(
&
b1
+
1
,
&
b2
);
// The array should not have been allocated anywhere near that first byte.
EXPECT_TRUE
(
arr
.
begin
()
<
&
b1
||
arr
.
begin
()
>
&
b1
+
512
);
// The next byte should have been allocated after the array.
EXPECT_EQ
(
arr
.
end
(),
&
b2
);
// Write to the array to make sure it's valid.
// Write to the array to make sure it's valid.
memset
(
arr
.
begin
(),
0xbe
,
arr
.
size
());
memset
(
arr
.
begin
(),
0xbe
,
arr
.
size
());
}
}
TEST
(
Arena
,
MultiSegment
)
{
TEST
(
Arena
,
MultiSegment
)
{
Arena
arena
(
24
);
// Sorry, this test makes assumptions about the size of ChunkHeader.
Arena
arena
(
sizeof
(
void
*
)
==
4
?
32
:
40
);
uint64_t
&
i1
=
arena
.
allocate
<
uint64_t
>
();
uint64_t
&
i1
=
arena
.
allocate
<
uint64_t
>
();
uint64_t
&
i2
=
arena
.
allocate
<
uint64_t
>
();
uint64_t
&
i2
=
arena
.
allocate
<
uint64_t
>
();
...
@@ -283,5 +306,96 @@ TEST(Arena, Strings) {
...
@@ -283,5 +306,96 @@ TEST(Arena, Strings) {
EXPECT_EQ
(
quux
.
end
()
+
1
,
corge
.
begin
());
EXPECT_EQ
(
quux
.
end
()
+
1
,
corge
.
begin
());
}
}
// I tried to use std::thread but it threw a pure-virtual exception. It's unclear if it's meant
// to be ready in GCC 4.7.
class
Thread
{
public
:
template
<
typename
Func
>
explicit
Thread
(
Func
&&
func
)
{
KJ_ASSERT
(
pthread_create
(
&
thread
,
nullptr
,
&
runThread
<
Decay
<
Func
>>
,
new
Decay
<
Func
>
(
kj
::
fwd
<
Func
>
(
func
)))
==
0
);
}
~
Thread
()
{
KJ_ASSERT
(
pthread_join
(
thread
,
nullptr
)
==
0
);
}
private
:
pthread_t
thread
;
template
<
typename
Func
>
static
void
*
runThread
(
void
*
ptr
)
{
Func
*
func
=
reinterpret_cast
<
Func
*>
(
ptr
);
KJ_DEFER
(
delete
func
);
(
*
func
)();
return
nullptr
;
}
};
struct
ThreadTestObject
{
ThreadTestObject
*
next
;
void
*
owner
;
// points into the owning thread's stack
ThreadTestObject
(
ThreadTestObject
*
next
,
void
*
owner
)
:
next
(
next
),
owner
(
owner
)
{}
~
ThreadTestObject
()
{
++
destructorCount
;
}
static
uint
destructorCount
;
};
uint
ThreadTestObject
::
destructorCount
=
0
;
TEST
(
Arena
,
Threads
)
{
// Test thread-safety. We allocate objects in four threads simultaneously, verify that they
// are not corrupted, then verify that their destructors are all called when the Arena is
// destroyed.
{
MutexGuarded
<
Arena
>
arena
;
// Func to run in each thread.
auto
threadFunc
=
[
&
]()
{
int
me
;
ThreadTestObject
*
head
=
nullptr
;
{
auto
lock
=
arena
.
lockShared
();
// Allocate a huge linked list.
for
(
uint
i
=
0
;
i
<
100000
;
i
++
)
{
head
=
&
lock
->
allocate
<
ThreadTestObject
>
(
head
,
&
me
);
}
}
// Wait until all other threads are done before verifying.
arena
.
lockExclusive
();
// Verify that the list hasn't been corrupted.
while
(
head
!=
nullptr
)
{
ASSERT_EQ
(
&
me
,
head
->
owner
);
head
=
head
->
next
;
}
};
{
auto
lock
=
arena
.
lockExclusive
();
Thread
thread1
(
threadFunc
);
Thread
thread2
(
threadFunc
);
Thread
thread3
(
threadFunc
);
Thread
thread4
(
threadFunc
);
// Wait for threads to be ready.
usleep
(
10000
);
auto
release
=
kj
::
mv
(
lock
);
// As we go out of scope, the lock will be released (since `release` is destroyed first),
// allowing all the threads to start running. We'll then join each thread.
}
EXPECT_EQ
(
0
,
ThreadTestObject
::
destructorCount
);
}
EXPECT_EQ
(
400000
,
ThreadTestObject
::
destructorCount
);
}
}
// namespace
}
// namespace
}
// namespace kj
}
// namespace kj
c++/src/kj/arena.c++
View file @
fc0c297c
...
@@ -27,11 +27,20 @@
...
@@ -27,11 +27,20 @@
namespace
kj
{
namespace
kj
{
Arena
::
Arena
(
size_t
chunkSize
)
:
state
(
chunkSize
)
{}
Arena
::
Arena
(
size_t
chunkSizeHint
)
:
state
(
kj
::
max
(
sizeof
(
ChunkHeader
),
chunkSizeHint
))
{}
Arena
::
Arena
(
ArrayPtr
<
byte
>
scratch
,
size_t
chunkSize
)
:
state
(
chunkSize
)
{
Arena
::
Arena
(
ArrayPtr
<
byte
>
scratch
)
state
.
pos
=
scratch
.
begin
();
:
state
(
kj
::
max
(
sizeof
(
ChunkHeader
),
scratch
.
size
()))
{
state
.
chunkEnd
=
scratch
.
end
();
if
(
scratch
.
size
()
>
sizeof
(
ChunkHeader
))
{
ChunkHeader
*
chunk
=
reinterpret_cast
<
ChunkHeader
*>
(
scratch
.
begin
());
chunk
->
end
=
scratch
.
end
();
chunk
->
pos
=
reinterpret_cast
<
byte
*>
(
chunk
+
1
);
chunk
->
next
=
nullptr
;
// Never actually observed.
// Don't place the chunk in the chunk list because it's not ours to delete. Just make it the
// current chunk so that we'll allocate from it until it is empty.
state
.
getWithoutLock
().
currentChunk
=
chunk
;
}
}
}
Arena
::~
Arena
()
noexcept
(
false
)
{
Arena
::~
Arena
()
noexcept
(
false
)
{
...
@@ -39,7 +48,7 @@ Arena::~Arena() noexcept(false) {
...
@@ -39,7 +48,7 @@ Arena::~Arena() noexcept(false) {
// called. This ensures that if the first pass throws an exception, remaining objects are still
// called. This ensures that if the first pass throws an exception, remaining objects are still
// destroyed. If the second pass throws, the program terminates, but any destructors that could
// destroyed. If the second pass throws, the program terminates, but any destructors that could
// throw should be using UnwindDetector to avoid this.
// throw should be using UnwindDetector to avoid this.
state
.
cleanup
();
state
.
getWithoutLock
().
cleanup
();
}
}
void
Arena
::
State
::
cleanup
()
{
void
Arena
::
State
::
cleanup
()
{
...
@@ -72,67 +81,108 @@ inline byte* alignTo(byte* p, uint alignment) {
...
@@ -72,67 +81,108 @@ inline byte* alignTo(byte* p, uint alignment) {
return
reinterpret_cast
<
byte
*>
((
i
+
mask
)
&
~
mask
);
return
reinterpret_cast
<
byte
*>
((
i
+
mask
)
&
~
mask
);
}
}
}
// namespace
inline
size_t
alignTo
(
size_t
s
,
uint
alignment
)
{
// Round the pointer up to the next aligned value.
void
*
Arena
::
allocateBytes
(
size_t
amount
,
uint
alignment
,
bool
hasDisposer
)
{
// Code below depends on power-of-two alignment and header sizes.
static_assert
(
isPowerOfTwo
(
sizeof
(
ChunkHeader
)),
"sizeof(ChunkHeader) is not a power of 2."
);
static_assert
(
isPowerOfTwo
(
sizeof
(
ObjectHeader
)),
"sizeof(ObjectHeader) is not a power of 2."
);
KJ_DASSERT
(
isPowerOfTwo
(
alignment
),
alignment
);
KJ_DASSERT
(
isPowerOfTwo
(
alignment
),
alignment
);
size_t
mask
=
alignment
-
1
;
return
(
s
+
mask
)
&
~
mask
;
}
// Offset we must apply if the allocated space is being prefixed with these headers.
}
// namespace
uint
chunkHeaderSize
=
kj
::
max
(
alignment
,
sizeof
(
ChunkHeader
));
uint
objectHeaderSize
=
kj
::
max
(
alignment
,
sizeof
(
ObjectHeader
));
void
*
Arena
::
allocateBytes
(
size_t
amount
,
uint
alignment
,
bool
hasDisposer
)
const
{
if
(
hasDisposer
)
{
if
(
hasDisposer
)
{
amount
+=
objectHeaderSize
;
alignment
=
kj
::
max
(
alignment
,
alignof
(
ObjectHeader
));
amount
+=
alignTo
(
sizeof
(
ObjectHeader
),
alignment
);
}
}
void
*
result
;
void
*
result
;
byte
*
alignedPos
=
alignTo
(
state
.
pos
,
alignment
);
byte
*
endPos
=
alignedPos
+
amount
;
for
(;;)
{
if
(
endPos
<=
state
.
chunkEnd
)
{
ChunkHeader
*
chunk
=
__atomic_load_n
(
&
state
.
getWithoutLock
().
currentChunk
,
__ATOMIC_ACQUIRE
);
// There's enough space in the current chunk.
result
=
alignedPos
;
if
(
chunk
==
nullptr
)
{
state
.
pos
=
alignedPos
+
amount
;
// No chunks allocated yet.
}
else
if
(
amount
+
chunkHeaderSize
>
state
.
chunkSize
||
result
=
allocateBytesFallback
(
amount
,
alignment
);
state
.
chunkEnd
-
state
.
pos
>
state
.
chunkSize
/
4
)
{
break
;
// This object is too big to fit in one chunk, or we'd waste more than a quarter of the chunk
}
// by starting a new one now. Instead, allocate the object in its own independent chunk.
ChunkHeader
*
newChunk
=
reinterpret_cast
<
ChunkHeader
*>
(
operator
new
(
chunkHeaderSize
+
amount
));
byte
*
pos
=
__atomic_load_n
(
&
chunk
->
pos
,
__ATOMIC_RELAXED
);
result
=
reinterpret_cast
<
byte
*>
(
newChunk
)
+
chunkHeaderSize
;
byte
*
alignedPos
=
alignTo
(
pos
,
alignment
);
newChunk
->
next
=
state
.
chunkList
;
byte
*
endPos
=
alignedPos
+
amount
;
state
.
chunkList
=
newChunk
;
// We don't update state.pos and state.chunkEnd because the new chunk has no extra space but
// Careful about pointer wrapping (e.g. if the chunk is near the end of the address space).
// the old chunk might.
if
(
chunk
->
end
-
endPos
<
0
)
{
}
else
{
// Not enough space.
// Allocate a new chunk.
result
=
allocateBytesFallback
(
amount
,
alignment
);
ChunkHeader
*
newChunk
=
reinterpret_cast
<
ChunkHeader
*>
(
operator
new
(
state
.
chunkSize
));
break
;
result
=
reinterpret_cast
<
byte
*>
(
newChunk
)
+
chunkHeaderSize
;
}
newChunk
->
next
=
state
.
chunkList
;
state
.
chunkList
=
newChunk
;
// There appears to be enough space in this chunk, unless another thread stole it.
state
.
pos
=
reinterpret_cast
<
byte
*>
(
result
)
+
amount
;
if
(
KJ_LIKELY
(
__atomic_compare_exchange_n
(
state
.
chunkEnd
=
reinterpret_cast
<
byte
*>
(
newChunk
)
+
state
.
chunkSize
;
&
chunk
->
pos
,
&
pos
,
endPos
,
true
,
__ATOMIC_RELAXED
,
__ATOMIC_RELAXED
)))
{
result
=
alignedPos
;
break
;
}
}
}
if
(
hasDisposer
)
{
if
(
hasDisposer
)
{
// Reserve space for the ObjectHeader, but don't add it to the object list yet.
// Reserve space for the ObjectHeader, but don't add it to the object list yet.
result
=
reinterpret_cast
<
byte
*>
(
result
)
+
objectHeaderSize
;
result
=
alignTo
(
reinterpret_cast
<
byte
*>
(
result
)
+
sizeof
(
ObjectHeader
),
alignment
)
;
}
}
KJ_DASSERT
(
reinterpret_cast
<
uintptr_t
>
(
result
)
%
alignment
==
0
);
return
result
;
return
result
;
}
}
StringPtr
Arena
::
copyString
(
StringPtr
content
)
{
void
*
Arena
::
allocateBytesFallback
(
size_t
amount
,
uint
alignment
)
const
{
auto
lock
=
state
.
lockExclusive
();
// We already know that the current chunk is out of space.
alignment
=
kj
::
max
(
alignment
,
alignof
(
ChunkHeader
));
amount
+=
alignTo
(
sizeof
(
ChunkHeader
),
alignment
);
while
(
lock
->
nextChunkSize
<
amount
)
{
lock
->
nextChunkSize
*=
2
;
}
byte
*
bytes
=
reinterpret_cast
<
byte
*>
(
operator
new
(
lock
->
nextChunkSize
));
ChunkHeader
*
newChunk
=
reinterpret_cast
<
ChunkHeader
*>
(
bytes
);
newChunk
->
next
=
lock
->
chunkList
;
newChunk
->
pos
=
bytes
+
amount
;
newChunk
->
end
=
bytes
+
lock
->
nextChunkSize
;
__atomic_store_n
(
&
lock
->
currentChunk
,
newChunk
,
__ATOMIC_RELEASE
);
lock
->
nextChunkSize
*=
2
;
byte
*
result
=
alignTo
(
bytes
+
sizeof
(
ChunkHeader
),
alignment
);
lock
->
chunkList
=
newChunk
;
return
result
;
}
StringPtr
Arena
::
copyString
(
StringPtr
content
)
const
{
char
*
data
=
reinterpret_cast
<
char
*>
(
allocateBytes
(
content
.
size
()
+
1
,
1
,
false
));
char
*
data
=
reinterpret_cast
<
char
*>
(
allocateBytes
(
content
.
size
()
+
1
,
1
,
false
));
memcpy
(
data
,
content
.
cStr
(),
content
.
size
()
+
1
);
memcpy
(
data
,
content
.
cStr
(),
content
.
size
()
+
1
);
return
StringPtr
(
data
,
content
.
size
());
return
StringPtr
(
data
,
content
.
size
());
}
}
void
Arena
::
setDestructor
(
void
*
ptr
,
void
(
*
destructor
)(
void
*
))
{
void
Arena
::
setDestructor
(
void
*
ptr
,
void
(
*
destructor
)(
void
*
))
const
{
ObjectHeader
*
header
=
reinterpret_cast
<
ObjectHeader
*>
(
ptr
)
-
1
;
ObjectHeader
*
header
=
reinterpret_cast
<
ObjectHeader
*>
(
ptr
)
-
1
;
KJ_DASSERT
(
reinterpret_cast
<
uintptr_t
>
(
header
)
%
alignof
(
ObjectHeader
)
==
0
);
header
->
destructor
=
destructor
;
header
->
destructor
=
destructor
;
header
->
next
=
state
.
objectList
;
header
->
next
=
state
.
getWithoutLock
().
objectList
;
state
.
objectList
=
header
;
// We can use relaxed atomics here because the object list is not actually traversed until the
// destructor, which needs to be synchronized in its own way.
while
(
!
__atomic_compare_exchange_n
(
&
state
.
getWithoutLock
().
objectList
,
&
header
->
next
,
header
,
true
,
__ATOMIC_RELAXED
,
__ATOMIC_RELAXED
))
{
// Retry.
}
}
}
}
// namespace kj
}
// namespace kj
c++/src/kj/arena.h
View file @
fc0c297c
...
@@ -27,40 +27,45 @@
...
@@ -27,40 +27,45 @@
#include "memory.h"
#include "memory.h"
#include "array.h"
#include "array.h"
#include "string.h"
#include "string.h"
#include "mutex.h"
namespace
kj
{
namespace
kj
{
class
Arena
{
class
Arena
{
// A class which allows several objects to be allocated in contiguous chunks of memory, then
// A class which allows several objects to be allocated in contiguous chunks of memory, then
// frees them all at once.
// frees them all at once.
//
// Allocating from the same Arena in multiple threads concurrently is safe but not particularly
// performant due to contention. The class could be optimized in the future to use per-thread
// chunks to solve this.
public
:
public
:
Arena
(
size_t
chunkSize
=
1024
);
explicit
Arena
(
size_t
chunkSizeHint
=
1024
);
// Create an Arena
that tries to allocate in chunks of the given size. It may deviate from the
// Create an Arena
. `chunkSizeHint` hints at where to start when allocating chunks, but is only
//
size if an object is too large to fit or to avoid excessive fragmentation. Each chunk has
//
a hint -- the Arena will, for example, allocate progressively larger chunks as time goes on,
//
a one-word header which is included in the chunk size
.
//
in order to reduce overall allocation overhead
.
Arena
(
ArrayPtr
<
byte
>
scratch
,
size_t
chunkSize
=
1024
);
explicit
Arena
(
ArrayPtr
<
byte
>
scratch
);
// Allocates from the given scratch space first, only resorting to the heap when it runs out.
// Allocates from the given scratch space first, only resorting to the heap when it runs out.
KJ_DISALLOW_COPY
(
Arena
);
KJ_DISALLOW_COPY
(
Arena
);
~
Arena
()
noexcept
(
false
);
~
Arena
()
noexcept
(
false
);
template
<
typename
T
,
typename
...
Params
>
template
<
typename
T
,
typename
...
Params
>
T
&
allocate
(
Params
&&
...
params
);
T
&
allocate
(
Params
&&
...
params
)
const
;
template
<
typename
T
>
template
<
typename
T
>
ArrayPtr
<
T
>
allocateArray
(
size_t
size
);
ArrayPtr
<
T
>
allocateArray
(
size_t
size
)
const
;
// Allocate an object or array of type T. If T has a non-trivial destructor, that destructor
// Allocate an object or array of type T. If T has a non-trivial destructor, that destructor
// will be run during the Arena's destructor. Such destructors are run in opposite order of
// will be run during the Arena's destructor. Such destructors are run in opposite order of
// allocation. Note that these methods must maintain a list of destructors to call, which has
// allocation. Note that these methods must maintain a list of destructors to call, which has
// overhead, but this overhead only applies if T has a non-trivial destructor.
// overhead, but this overhead only applies if T has a non-trivial destructor.
template
<
typename
T
,
typename
...
Params
>
template
<
typename
T
,
typename
...
Params
>
Own
<
T
>
allocateOwn
(
Params
&&
...
params
);
Own
<
T
>
allocateOwn
(
Params
&&
...
params
)
const
;
template
<
typename
T
>
template
<
typename
T
>
Array
<
T
>
allocateOwnArray
(
size_t
size
);
Array
<
T
>
allocateOwnArray
(
size_t
size
)
const
;
template
<
typename
T
>
template
<
typename
T
>
ArrayBuilder
<
T
>
allocateOwnArrayBuilder
(
size_t
capacity
);
ArrayBuilder
<
T
>
allocateOwnArrayBuilder
(
size_t
capacity
)
const
;
// Allocate an object or array of type T. Destructors are executed when the returned Own<T>
// Allocate an object or array of type T. Destructors are executed when the returned Own<T>
// or Array<T> goes out-of-scope, which must happen before the Arena is destroyed. This variant
// or Array<T> goes out-of-scope, which must happen before the Arena is destroyed. This variant
// is useful when you need to control when the destructor is called. This variant also avoids
// is useful when you need to control when the destructor is called. This variant also avoids
...
@@ -68,16 +73,18 @@ public:
...
@@ -68,16 +73,18 @@ public:
// slightly more efficient.
// slightly more efficient.
template
<
typename
T
>
template
<
typename
T
>
inline
T
&
copy
(
T
&&
value
)
{
return
allocate
<
Decay
<
T
>>
(
kj
::
fwd
<
T
>
(
value
));
}
inline
T
&
copy
(
T
&&
value
)
const
{
return
allocate
<
Decay
<
T
>>
(
kj
::
fwd
<
T
>
(
value
));
}
// Allocate a copy of the given value in the arena. This is just a shortcut for calling the
// Allocate a copy of the given value in the arena. This is just a shortcut for calling the
// type's copy (or move) constructor.
// type's copy (or move) constructor.
StringPtr
copyString
(
StringPtr
content
);
StringPtr
copyString
(
StringPtr
content
)
const
;
// Make a copy of the given string inside the arena, and return a pointer to the copy.
// Make a copy of the given string inside the arena, and return a pointer to the copy.
private
:
private
:
struct
ChunkHeader
{
struct
ChunkHeader
{
ChunkHeader
*
next
;
ChunkHeader
*
next
;
byte
*
pos
;
// first unallocated byte in this chunk
byte
*
end
;
// end of this chunk
};
};
struct
ObjectHeader
{
struct
ObjectHeader
{
void
(
*
destructor
)(
void
*
);
void
(
*
destructor
)(
void
*
);
...
@@ -85,15 +92,15 @@ private:
...
@@ -85,15 +92,15 @@ private:
};
};
struct
State
{
struct
State
{
size_t
c
hunkSize
;
size_t
nextC
hunkSize
;
ChunkHeader
*
chunkList
;
ChunkHeader
*
chunkList
;
ObjectHeader
*
objectList
;
mutable
ObjectHeader
*
objectList
;
byte
*
pos
;
byte
*
chunkEnd
;
inline
State
(
size_t
chunkSize
)
ChunkHeader
*
currentChunk
;
:
chunkSize
(
chunkSize
),
chunkList
(
nullptr
),
objectList
(
nullptr
),
pos
(
nullptr
),
chunkEnd
(
nullptr
)
{}
inline
State
(
size_t
nextChunkSize
)
:
nextChunkSize
(
nextChunkSize
),
chunkList
(
nullptr
),
objectList
(
nullptr
),
currentChunk
(
nullptr
)
{}
inline
~
State
()
noexcept
(
false
)
{
cleanup
();
}
inline
~
State
()
noexcept
(
false
)
{
cleanup
();
}
void
cleanup
();
void
cleanup
();
...
@@ -101,13 +108,16 @@ private:
...
@@ -101,13 +108,16 @@ private:
// left in a consistent state, such that if cleanup() is called again, it will pick up where
// left in a consistent state, such that if cleanup() is called again, it will pick up where
// it left off.
// it left off.
};
};
State
state
;
MutexGuarded
<
State
>
state
;
void
*
allocateBytes
(
size_t
amount
,
uint
alignment
,
bool
hasDisposer
);
void
*
allocateBytes
(
size_t
amount
,
uint
alignment
,
bool
hasDisposer
)
const
;
// Allocate the given number of bytes. `hasDisposer` must be true if `setDisposer()` may be
// Allocate the given number of bytes. `hasDisposer` must be true if `setDisposer()` may be
// called on this pointer later.
// called on this pointer later.
void
setDestructor
(
void
*
ptr
,
void
(
*
destructor
)(
void
*
));
void
*
allocateBytesFallback
(
size_t
amount
,
uint
alignment
)
const
;
// Fallback used when the current chunk is out of space.
void
setDestructor
(
void
*
ptr
,
void
(
*
destructor
)(
void
*
))
const
;
// Schedule the given destructor to be executed when the Arena is destroyed. `ptr` must be a
// Schedule the given destructor to be executed when the Arena is destroyed. `ptr` must be a
// pointer previously returned by an `allocateBytes()` call for which `hasDisposer` was true.
// pointer previously returned by an `allocateBytes()` call for which `hasDisposer` was true.
...
@@ -130,7 +140,7 @@ private:
...
@@ -130,7 +140,7 @@ private:
// Inline implementation details
// Inline implementation details
template
<
typename
T
,
typename
...
Params
>
template
<
typename
T
,
typename
...
Params
>
T
&
Arena
::
allocate
(
Params
&&
...
params
)
{
T
&
Arena
::
allocate
(
Params
&&
...
params
)
const
{
T
&
result
=
*
reinterpret_cast
<
T
*>
(
allocateBytes
(
T
&
result
=
*
reinterpret_cast
<
T
*>
(
allocateBytes
(
sizeof
(
T
),
alignof
(
T
),
!
__has_trivial_destructor
(
T
)));
sizeof
(
T
),
alignof
(
T
),
!
__has_trivial_destructor
(
T
)));
if
(
!
__has_trivial_constructor
(
T
)
||
sizeof
...(
Params
)
>
0
)
{
if
(
!
__has_trivial_constructor
(
T
)
||
sizeof
...(
Params
)
>
0
)
{
...
@@ -143,7 +153,7 @@ T& Arena::allocate(Params&&... params) {
...
@@ -143,7 +153,7 @@ T& Arena::allocate(Params&&... params) {
}
}
template
<
typename
T
>
template
<
typename
T
>
ArrayPtr
<
T
>
Arena
::
allocateArray
(
size_t
size
)
{
ArrayPtr
<
T
>
Arena
::
allocateArray
(
size_t
size
)
const
{
if
(
__has_trivial_destructor
(
T
))
{
if
(
__has_trivial_destructor
(
T
))
{
ArrayPtr
<
T
>
result
=
ArrayPtr
<
T
>
result
=
arrayPtr
(
reinterpret_cast
<
T
*>
(
allocateBytes
(
arrayPtr
(
reinterpret_cast
<
T
*>
(
allocateBytes
(
...
@@ -179,7 +189,7 @@ ArrayPtr<T> Arena::allocateArray(size_t size) {
...
@@ -179,7 +189,7 @@ ArrayPtr<T> Arena::allocateArray(size_t size) {
}
}
template
<
typename
T
,
typename
...
Params
>
template
<
typename
T
,
typename
...
Params
>
Own
<
T
>
Arena
::
allocateOwn
(
Params
&&
...
params
)
{
Own
<
T
>
Arena
::
allocateOwn
(
Params
&&
...
params
)
const
{
T
&
result
=
*
reinterpret_cast
<
T
*>
(
allocateBytes
(
sizeof
(
T
),
alignof
(
T
),
false
));
T
&
result
=
*
reinterpret_cast
<
T
*>
(
allocateBytes
(
sizeof
(
T
),
alignof
(
T
),
false
));
if
(
!
__has_trivial_constructor
(
T
)
||
sizeof
...(
Params
)
>
0
)
{
if
(
!
__has_trivial_constructor
(
T
)
||
sizeof
...(
Params
)
>
0
)
{
ctor
(
result
,
kj
::
fwd
<
Params
>
(
params
)...);
ctor
(
result
,
kj
::
fwd
<
Params
>
(
params
)...);
...
@@ -188,7 +198,7 @@ Own<T> Arena::allocateOwn(Params&&... params) {
...
@@ -188,7 +198,7 @@ Own<T> Arena::allocateOwn(Params&&... params) {
}
}
template
<
typename
T
>
template
<
typename
T
>
Array
<
T
>
Arena
::
allocateOwnArray
(
size_t
size
)
{
Array
<
T
>
Arena
::
allocateOwnArray
(
size_t
size
)
const
{
ArrayBuilder
<
T
>
result
=
allocateOwnArrayBuilder
<
T
>
(
size
);
ArrayBuilder
<
T
>
result
=
allocateOwnArrayBuilder
<
T
>
(
size
);
for
(
size_t
i
=
0
;
i
<
size
;
i
++
)
{
for
(
size_t
i
=
0
;
i
<
size
;
i
++
)
{
result
.
add
();
result
.
add
();
...
@@ -197,7 +207,7 @@ Array<T> Arena::allocateOwnArray(size_t size) {
...
@@ -197,7 +207,7 @@ Array<T> Arena::allocateOwnArray(size_t size) {
}
}
template
<
typename
T
>
template
<
typename
T
>
ArrayBuilder
<
T
>
Arena
::
allocateOwnArrayBuilder
(
size_t
capacity
)
{
ArrayBuilder
<
T
>
Arena
::
allocateOwnArrayBuilder
(
size_t
capacity
)
const
{
return
ArrayBuilder
<
T
>
(
return
ArrayBuilder
<
T
>
(
reinterpret_cast
<
T
*>
(
allocateBytes
(
sizeof
(
T
)
*
capacity
,
alignof
(
T
),
false
)),
reinterpret_cast
<
T
*>
(
allocateBytes
(
sizeof
(
T
)
*
capacity
,
alignof
(
T
),
false
)),
capacity
,
DestructorOnlyArrayDisposer
::
instance
);
capacity
,
DestructorOnlyArrayDisposer
::
instance
);
...
...
c++/src/kj/mutex-test.c++
View file @
fc0c297c
...
@@ -63,11 +63,11 @@ TEST(Mutex, MutexGuarded) {
...
@@ -63,11 +63,11 @@ TEST(Mutex, MutexGuarded) {
MutexGuarded
<
uint
>
value
(
123
);
MutexGuarded
<
uint
>
value
(
123
);
{
{
Locked
<
uint
>
lock
=
value
.
lock
();
Locked
<
uint
>
lock
=
value
.
lock
Exclusive
();
EXPECT_EQ
(
123
,
*
lock
);
EXPECT_EQ
(
123
,
*
lock
);
Thread
thread
([
&
]()
{
Thread
thread
([
&
]()
{
Locked
<
uint
>
threadLock
=
value
.
lock
();
Locked
<
uint
>
threadLock
=
value
.
lock
Exclusive
();
EXPECT_EQ
(
456
,
*
threadLock
);
EXPECT_EQ
(
456
,
*
threadLock
);
*
threadLock
=
789
;
*
threadLock
=
789
;
});
});
...
@@ -78,13 +78,13 @@ TEST(Mutex, MutexGuarded) {
...
@@ -78,13 +78,13 @@ TEST(Mutex, MutexGuarded) {
auto
earlyRelease
=
kj
::
mv
(
lock
);
auto
earlyRelease
=
kj
::
mv
(
lock
);
}
}
EXPECT_EQ
(
789
,
*
value
.
lock
());
EXPECT_EQ
(
789
,
*
value
.
lock
Exclusive
());
{
{
auto
rlock1
=
value
.
lock
ForRea
d
();
auto
rlock1
=
value
.
lock
Share
d
();
Thread
thread2
([
&
]()
{
Thread
thread2
([
&
]()
{
Locked
<
uint
>
threadLock
=
value
.
lock
();
Locked
<
uint
>
threadLock
=
value
.
lock
Exclusive
();
*
threadLock
=
321
;
*
threadLock
=
321
;
});
});
...
@@ -92,11 +92,11 @@ TEST(Mutex, MutexGuarded) {
...
@@ -92,11 +92,11 @@ TEST(Mutex, MutexGuarded) {
EXPECT_EQ
(
789
,
*
rlock1
);
EXPECT_EQ
(
789
,
*
rlock1
);
{
{
auto
rlock2
=
value
.
lock
ForRea
d
();
auto
rlock2
=
value
.
lock
Share
d
();
EXPECT_EQ
(
789
,
*
rlock2
);
EXPECT_EQ
(
789
,
*
rlock2
);
auto
rlock3
=
value
.
lock
ForRea
d
();
auto
rlock3
=
value
.
lock
Share
d
();
EXPECT_EQ
(
789
,
*
rlock3
);
EXPECT_EQ
(
789
,
*
rlock3
);
auto
rlock4
=
value
.
lock
ForRea
d
();
auto
rlock4
=
value
.
lock
Share
d
();
EXPECT_EQ
(
789
,
*
rlock4
);
EXPECT_EQ
(
789
,
*
rlock4
);
}
}
...
@@ -105,7 +105,7 @@ TEST(Mutex, MutexGuarded) {
...
@@ -105,7 +105,7 @@ TEST(Mutex, MutexGuarded) {
auto
earlyRelease
=
kj
::
mv
(
rlock1
);
auto
earlyRelease
=
kj
::
mv
(
rlock1
);
}
}
EXPECT_EQ
(
321
,
*
value
.
lock
());
EXPECT_EQ
(
321
,
*
value
.
lock
Exclusive
());
}
}
TEST
(
Mutex
,
Lazy
)
{
TEST
(
Mutex
,
Lazy
)
{
...
...
c++/src/kj/mutex.c++
View file @
fc0c297c
...
@@ -50,15 +50,18 @@ Mutex::~Mutex() {
...
@@ -50,15 +50,18 @@ Mutex::~Mutex() {
KJ_PTHREAD_CLEANUP
(
pthread_rwlock_destroy
(
&
mutex
));
KJ_PTHREAD_CLEANUP
(
pthread_rwlock_destroy
(
&
mutex
));
}
}
void
Mutex
::
lock
()
{
void
Mutex
::
lock
(
Exclusivity
exclusivity
)
{
KJ_PTHREAD_CALL
(
pthread_rwlock_wrlock
(
&
mutex
));
switch
(
exclusivity
)
{
}
case
EXCLUSIVE
:
KJ_PTHREAD_CALL
(
pthread_rwlock_wrlock
(
&
mutex
));
void
Mutex
::
readLock
()
{
break
;
KJ_PTHREAD_CALL
(
pthread_rwlock_rdlock
(
&
mutex
));
case
SHARED
:
KJ_PTHREAD_CALL
(
pthread_rwlock_rdlock
(
&
mutex
));
break
;
}
}
}
void
Mutex
::
unlock
(
bool
lockedForRead
)
{
void
Mutex
::
unlock
(
Exclusivity
exclusivity
)
{
KJ_PTHREAD_CALL
(
pthread_rwlock_unlock
(
&
mutex
));
KJ_PTHREAD_CALL
(
pthread_rwlock_unlock
(
&
mutex
));
}
}
...
...
c++/src/kj/mutex.h
View file @
fc0c297c
...
@@ -47,9 +47,13 @@ public:
...
@@ -47,9 +47,13 @@ public:
~
Mutex
();
~
Mutex
();
KJ_DISALLOW_COPY
(
Mutex
);
KJ_DISALLOW_COPY
(
Mutex
);
void
lock
();
enum
Exclusivity
{
void
readLock
();
EXCLUSIVE
,
void
unlock
(
bool
lockedForRead
);
SHARED
};
void
lock
(
Exclusivity
exclusivity
);
void
unlock
(
Exclusivity
exclusivity
);
private
:
private
:
mutable
pthread_rwlock_t
mutex
;
mutable
pthread_rwlock_t
mutex
;
...
@@ -97,7 +101,9 @@ public:
...
@@ -97,7 +101,9 @@ public:
other
.
mutex
=
nullptr
;
other
.
mutex
=
nullptr
;
other
.
ptr
=
nullptr
;
other
.
ptr
=
nullptr
;
}
}
inline
~
Locked
()
{
if
(
mutex
!=
nullptr
)
mutex
->
unlock
(
isConst
<
T
>
());
}
inline
~
Locked
()
{
if
(
mutex
!=
nullptr
)
mutex
->
unlock
(
isConst
<
T
>
()
?
_
::
Mutex
::
SHARED
:
_
::
Mutex
::
EXCLUSIVE
);
}
inline
Locked
&
operator
=
(
Locked
&&
other
)
{
inline
Locked
&
operator
=
(
Locked
&&
other
)
{
if
(
mutex
!=
nullptr
)
mutex
->
unlock
(
isConst
<
T
>
());
if
(
mutex
!=
nullptr
)
mutex
->
unlock
(
isConst
<
T
>
());
...
@@ -139,8 +145,8 @@ public:
...
@@ -139,8 +145,8 @@ public:
explicit
MutexGuarded
(
Params
&&
...
params
);
explicit
MutexGuarded
(
Params
&&
...
params
);
// Initialize the mutex-guarded object by passing the given parameters to its constructor.
// Initialize the mutex-guarded object by passing the given parameters to its constructor.
Locked
<
T
>
lock
()
const
;
Locked
<
T
>
lock
Exclusive
()
const
;
//
Locks the mutex and returns the guarded objec
t. The returned `Locked<T>` can be passed by
//
Exclusively locks the object and returns i
t. The returned `Locked<T>` can be passed by
// move, similar to `Own<T>`.
// move, similar to `Own<T>`.
//
//
// This method is declared `const` in accordance with KJ style rules which say that constness
// This method is declared `const` in accordance with KJ style rules which say that constness
...
@@ -149,9 +155,9 @@ public:
...
@@ -149,9 +155,9 @@ public:
// be shared between threads, its methods should be const, even though locking it produces a
// be shared between threads, its methods should be const, even though locking it produces a
// non-const pointer to the contained object.
// non-const pointer to the contained object.
Locked
<
const
T
>
lock
ForRea
d
()
const
;
Locked
<
const
T
>
lock
Share
d
()
const
;
// Lock the value for
read-only access. Multiple read-only locks can be taken concurrently, as
// Lock the value for
shared access. Multiple shared locks can be taken concurrently, but cannot
//
long as there are no writers
.
//
be held at the same time as a non-shared lock
.
inline
const
T
&
getWithoutLock
()
const
{
return
value
;
}
inline
const
T
&
getWithoutLock
()
const
{
return
value
;
}
inline
T
&
getWithoutLock
()
{
return
value
;
}
inline
T
&
getWithoutLock
()
{
return
value
;
}
...
@@ -205,14 +211,14 @@ inline MutexGuarded<T>::MutexGuarded(Params&&... params)
...
@@ -205,14 +211,14 @@ inline MutexGuarded<T>::MutexGuarded(Params&&... params)
:
value
(
kj
::
fwd
<
Params
>
(
params
)...)
{}
:
value
(
kj
::
fwd
<
Params
>
(
params
)...)
{}
template
<
typename
T
>
template
<
typename
T
>
inline
Locked
<
T
>
MutexGuarded
<
T
>::
lock
()
const
{
inline
Locked
<
T
>
MutexGuarded
<
T
>::
lock
Exclusive
()
const
{
mutex
.
lock
();
mutex
.
lock
(
_
::
Mutex
::
EXCLUSIVE
);
return
Locked
<
T
>
(
mutex
,
value
);
return
Locked
<
T
>
(
mutex
,
value
);
}
}
template
<
typename
T
>
template
<
typename
T
>
inline
Locked
<
const
T
>
MutexGuarded
<
T
>::
lock
ForRea
d
()
const
{
inline
Locked
<
const
T
>
MutexGuarded
<
T
>::
lock
Share
d
()
const
{
mutex
.
readLock
(
);
mutex
.
lock
(
_
::
Mutex
::
SHARED
);
return
Locked
<
const
T
>
(
mutex
,
value
);
return
Locked
<
const
T
>
(
mutex
,
value
);
}
}
...
...
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