Skip to content

Commit ea2c923

Browse files
committed
8323807: Async UL: Add a stalling mode to async UL
Reviewed-by: dholmes, aboldtch
1 parent e7d4b36 commit ea2c923

File tree

9 files changed

+241
-61
lines changed

9 files changed

+241
-61
lines changed

src/hotspot/share/logging/logAsyncWriter.cpp

Lines changed: 103 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -26,45 +26,63 @@
2626
#include "logging/logConfiguration.hpp"
2727
#include "logging/logFileOutput.hpp"
2828
#include "logging/logFileStreamOutput.hpp"
29-
#include "logging/logHandle.hpp"
29+
#include "memory/allocation.hpp"
3030
#include "memory/resourceArea.hpp"
3131
#include "runtime/atomic.hpp"
32-
#include "runtime/os.inline.hpp"
33-
#include "runtime/globals.hpp"
3432

33+
class AsyncLogWriter::Locker : public StackObj {
34+
Thread*& _holder;
35+
PlatformMonitor& _lock;
3536

36-
class AsyncLogWriter::AsyncLogLocker : public StackObj {
37-
static Thread* _holder;
3837
public:
39-
static Thread* current_holder() { return _holder; }
40-
AsyncLogLocker() {
41-
assert(_instance != nullptr, "AsyncLogWriter::_lock is unavailable");
42-
_instance->_lock.lock();
38+
Locker(Thread*& holder, PlatformMonitor& lock)
39+
: _holder(holder),
40+
_lock(lock) {
41+
_lock.lock();
4342
_holder = Thread::current_or_null();
4443
}
4544

46-
~AsyncLogLocker() {
45+
~Locker() {
4746
assert(_holder == Thread::current_or_null(), "must be");
4847
_holder = nullptr;
49-
_instance->_lock.unlock();
48+
_lock.unlock();
49+
}
50+
51+
void notify() {
52+
_lock.notify();
5053
}
5154

5255
void wait() {
5356
Thread* saved_holder = _holder;
5457
_holder = nullptr;
55-
_instance->_lock.wait(0/* no timeout */);
58+
_lock.wait(0 /* no timeout */);
5659
_holder = saved_holder;
5760
}
5861
};
5962

60-
Thread* AsyncLogWriter::AsyncLogLocker::_holder = nullptr;
63+
class AsyncLogWriter::ProducerLocker : public Locker {
64+
static Thread* _holder;
65+
public:
66+
static Thread* current_holder() { return _holder; }
67+
ProducerLocker() : Locker(_holder, _instance->_producer_lock) {}
68+
};
69+
70+
class AsyncLogWriter::ConsumerLocker : public Locker {
71+
static Thread* _holder;
72+
public:
73+
static Thread* current_holder() { return _holder; }
74+
ConsumerLocker() : Locker(_holder, _instance->_consumer_lock) {}
75+
};
76+
77+
Thread* AsyncLogWriter::ProducerLocker::_holder = nullptr;
78+
Thread* AsyncLogWriter::ConsumerLocker::_holder = nullptr;
6179

6280
// LogDecorator::None applies to 'constant initialization' because of its constexpr constructor.
6381
const LogDecorations& AsyncLogWriter::None = LogDecorations(LogLevel::Warning, LogTagSetMapping<LogTag::__NO_TAG>::tagset(),
6482
LogDecorators::None);
6583

66-
bool AsyncLogWriter::Buffer::push_back(LogFileStreamOutput* output, const LogDecorations& decorations, const char* msg) {
67-
const size_t len = strlen(msg);
84+
bool AsyncLogWriter::Buffer::push_back(LogFileStreamOutput* output, const LogDecorations& decorations, const char* msg, const size_t msg_len) {
85+
const size_t len = msg_len;
6886
const size_t sz = Message::calc_size(len);
6987
const bool is_token = output == nullptr;
7088
// Always leave headroom for the flush token. Pushing a token must succeed.
@@ -80,7 +98,7 @@ bool AsyncLogWriter::Buffer::push_back(LogFileStreamOutput* output, const LogDec
8098
}
8199

82100
void AsyncLogWriter::Buffer::push_flush_token() {
83-
bool result = push_back(nullptr, AsyncLogWriter::None, "");
101+
bool result = push_back(nullptr, AsyncLogWriter::None, "", 0);
84102
assert(result, "fail to enqueue the flush token.");
85103
}
86104

@@ -89,22 +107,45 @@ void AsyncLogWriter::enqueue_locked(LogFileStreamOutput* output, const LogDecora
89107
// client should use "" instead.
90108
assert(msg != nullptr, "enqueuing a null message!");
91109

92-
if (!_buffer->push_back(output, decorations, msg)) {
93-
bool p_created;
94-
uint32_t* counter = _stats.put_if_absent(output, 0, &p_created);
95-
*counter = *counter + 1;
96-
return;
97-
}
110+
size_t msg_len = strlen(msg);
111+
void* stalled_message = nullptr;
112+
{
113+
ConsumerLocker clocker;
114+
if (_buffer->push_back(output, decorations, msg, msg_len)) {
115+
_data_available = true;
116+
clocker.notify();
117+
return;
118+
}
98119

99-
_data_available = true;
100-
_lock.notify();
120+
if (LogConfiguration::async_mode() == LogConfiguration::AsyncMode::Stall) {
121+
size_t size = Message::calc_size(msg_len);
122+
stalled_message = os::malloc(size, mtLogging);
123+
if (stalled_message == nullptr) {
124+
// Out of memory. We bail without any notice.
125+
// Some other part of the system will probably fail later.
126+
return;
127+
}
128+
_stalled_message = new (stalled_message) Message(output, decorations, msg, msg_len);
129+
_data_available = true;
130+
clocker.notify();
131+
// Note: we still hold the producer lock so cannot race against other threads trying to log a message
132+
while (_stalled_message != nullptr) {
133+
clocker.wait();
134+
}
135+
} else {
136+
bool p_created;
137+
uint32_t* counter = _stats.put_if_absent(output, 0, &p_created);
138+
*counter = *counter + 1;
139+
}
140+
} // ConsumerLocker out of scope
141+
os::free(stalled_message);
101142
}
102143

103144
// This function checks for cases where continuing with asynchronous logging may lead to stability issues, such as a deadlock.
104145
// If this returns false then we give up on logging asynchronously and do so synchronously instead.
105146
bool AsyncLogWriter::is_enqueue_allowed() {
106147
AsyncLogWriter* alw = AsyncLogWriter::instance();
107-
Thread* holding_thread = AsyncLogWriter::AsyncLogLocker::current_holder();
148+
Thread* holding_thread = AsyncLogWriter::ProducerLocker::current_holder();
108149
Thread* this_thread = Thread::current_or_null();
109150
if (this_thread == nullptr) {
110151
// The current thread is unattached.
@@ -142,7 +183,7 @@ bool AsyncLogWriter::enqueue(LogFileStreamOutput& output, const LogDecorations&
142183
return false;
143184
}
144185

145-
AsyncLogLocker locker;
186+
ProducerLocker plocker;
146187

147188
#ifdef ASSERT
148189
if (TestingAsyncLoggingDeathTest || TestingAsyncLoggingDeathTestNoCrash) {
@@ -162,17 +203,21 @@ bool AsyncLogWriter::enqueue(LogFileStreamOutput& output, LogMessageBuffer::Iter
162203
}
163204

164205
// If we get here we know the AsyncLogWriter is initialized.
165-
AsyncLogLocker locker;
206+
ProducerLocker plocker;
166207
for (; !msg_iterator.is_at_end(); msg_iterator++) {
167208
AsyncLogWriter::instance()->enqueue_locked(&output, msg_iterator.decorations(), msg_iterator.message());
168209
}
169210
return true;
170211
}
171212

172213
AsyncLogWriter::AsyncLogWriter()
173-
: _flush_sem(0), _lock(), _data_available(false),
174-
_initialized(false),
175-
_stats() {
214+
: _flush_sem(0),
215+
_producer_lock(),
216+
_consumer_lock(),
217+
_data_available(false),
218+
_initialized(false),
219+
_stats(),
220+
_stalled_message(nullptr) {
176221

177222
size_t size = AsyncLogBufferSize / 2;
178223
_buffer = new Buffer(size);
@@ -185,7 +230,7 @@ AsyncLogWriter::AsyncLogWriter()
185230
}
186231
}
187232

188-
void AsyncLogWriter::write(AsyncLogMap<AnyObj::RESOURCE_AREA>& snapshot) {
233+
bool AsyncLogWriter::write(AsyncLogMap<AnyObj::RESOURCE_AREA>& snapshot) {
189234
int req = 0;
190235
auto it = _buffer_staging->iterator();
191236
while (it.hasNext()) {
@@ -213,20 +258,21 @@ void AsyncLogWriter::write(AsyncLogMap<AnyObj::RESOURCE_AREA>& snapshot) {
213258

214259
if (req > 0) {
215260
assert(req == 1, "Only one token is allowed in queue. AsyncLogWriter::flush() is NOT MT-safe!");
216-
_flush_sem.signal(req);
261+
return true;
217262
}
263+
return false;
218264
}
219265

220266
void AsyncLogWriter::run() {
221267
while (true) {
222268
ResourceMark rm;
223269
AsyncLogMap<AnyObj::RESOURCE_AREA> snapshot;
224270
{
225-
AsyncLogLocker locker;
226-
271+
ConsumerLocker clocker;
227272
while (!_data_available) {
228-
locker.wait();
273+
clocker.wait();
229274
}
275+
230276
// Only doing a swap and statistics under the lock to
231277
// guarantee that I/O jobs don't block logsites.
232278
_buffer_staging->reset();
@@ -243,7 +289,23 @@ void AsyncLogWriter::run() {
243289
});
244290
_data_available = false;
245291
}
246-
write(snapshot);
292+
293+
bool saw_flush_token = write(snapshot);
294+
295+
// Any stalled message must be written *after* the buffer has been written.
296+
// This is because we try hard to output messages in program-order.
297+
if (_stalled_message != nullptr) {
298+
assert(LogConfiguration::async_mode() == LogConfiguration::AsyncMode::Stall, "must be");
299+
ConsumerLocker clocker;
300+
Message* m = (Message*)_stalled_message;
301+
m->output()->write_blocking(m->decorations(), m->message());
302+
_stalled_message = nullptr;
303+
clocker.notify();
304+
}
305+
306+
if (saw_flush_token) {
307+
_flush_sem.signal(1);
308+
}
247309
}
248310
}
249311

@@ -281,19 +343,20 @@ AsyncLogWriter* AsyncLogWriter::instance() {
281343
void AsyncLogWriter::flush() {
282344
if (_instance != nullptr) {
283345
{
284-
AsyncLogLocker locker;
346+
ProducerLocker plocker;
347+
ConsumerLocker clocker;
285348
// Push directly in-case we are at logical max capacity, as this must not get dropped.
286349
_instance->_buffer->push_flush_token();
287350
_instance->_data_available = true;
288-
_instance->_lock.notify();
351+
clocker.notify();
289352
}
290353

291354
_instance->_flush_sem.wait();
292355
}
293356
}
294357

295358
AsyncLogWriter::BufferUpdater::BufferUpdater(size_t newsize) {
296-
AsyncLogLocker locker;
359+
ConsumerLocker clocker;
297360
auto p = AsyncLogWriter::_instance;
298361

299362
_buf1 = p->_buffer;
@@ -307,7 +370,7 @@ AsyncLogWriter::BufferUpdater::~BufferUpdater() {
307370
auto p = AsyncLogWriter::_instance;
308371

309372
{
310-
AsyncLogLocker locker;
373+
ConsumerLocker clocker;
311374

312375
delete p->_buffer;
313376
delete p->_buffer_staging;

src/hotspot/share/logging/logAsyncWriter.hpp

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "logging/logMessageBuffer.hpp"
3030
#include "memory/allocation.hpp"
3131
#include "runtime/mutex.hpp"
32+
#include "runtime/os.inline.hpp"
3233
#include "runtime/nonJavaThread.hpp"
3334
#include "runtime/semaphore.hpp"
3435
#include "utilities/resourceHash.hpp"
@@ -59,7 +60,9 @@ class LogFileStreamOutput;
5960
class AsyncLogWriter : public NonJavaThread {
6061
friend class AsyncLogTest;
6162
friend class AsyncLogTest_logBuffer_vm_Test;
62-
class AsyncLogLocker;
63+
class Locker;
64+
class ProducerLocker;
65+
class ConsumerLocker;
6366

6467
// account for dropped messages
6568
template <AnyObj::allocation_type ALLOC_TYPE>
@@ -125,7 +128,7 @@ class AsyncLogWriter : public NonJavaThread {
125128
}
126129

127130
void push_flush_token();
128-
bool push_back(LogFileStreamOutput* output, const LogDecorations& decorations, const char* msg);
131+
bool push_back(LogFileStreamOutput* output, const LogDecorations& decorations, const char* msg, const size_t msg_len);
129132

130133
void reset() {
131134
// Ensure _pos is Message-aligned
@@ -159,20 +162,31 @@ class AsyncLogWriter : public NonJavaThread {
159162
static AsyncLogWriter* _instance;
160163
Semaphore _flush_sem;
161164
// Can't use a Monitor here as we need a low-level API that can be used without Thread::current().
162-
PlatformMonitor _lock;
165+
// Producers take both locks in the order producer lock and then consumer lock.
166+
// The consumer protects the buffers and performs all communication between producer and consumer via wait/notify.
167+
// This allows a producer to await progress from the consumer thread (by only releasing the producer lock)), whilst preventing all other producers from progressing.
168+
PlatformMonitor _producer_lock;
169+
PlatformMonitor _consumer_lock;
163170
bool _data_available;
171+
// _initialized is set to true if the constructor succeeds
164172
volatile bool _initialized;
165173
AsyncLogMap<AnyObj::C_HEAP> _stats;
166174

167175
// ping-pong buffers
168176
Buffer* _buffer;
169177
Buffer* _buffer_staging;
170178

179+
// Stalled message
180+
// Stalling is implemented by the producer writing to _stalled_message, notifying the consumer lock and releasing it.
181+
// The consumer will then write all of the current buffers' content and then write the stalled message, at the end notifying the consumer lock and releasing it for the
182+
// owning producer thread of the stalled message. This thread will finally release both locks in order, allowing for other producers to continue.
183+
volatile Message* _stalled_message;
184+
171185
static const LogDecorations& None;
172186

173187
AsyncLogWriter();
174188
void enqueue_locked(LogFileStreamOutput* output, const LogDecorations& decorations, const char* msg);
175-
void write(AsyncLogMap<AnyObj::RESOURCE_AREA>& snapshot);
189+
bool write(AsyncLogMap<AnyObj::RESOURCE_AREA>& snapshot);
176190
void run() override;
177191
void pre_run() override {
178192
NonJavaThread::pre_run();

src/hotspot/share/logging/logConfiguration.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -636,10 +636,15 @@ void LogConfiguration::print_command_line_help(outputStream* out) {
636636
out->cr();
637637

638638
out->print_cr("Asynchronous logging (off by default):");
639-
out->print_cr(" -Xlog:async");
639+
out->print_cr(" -Xlog:async[:[mode]]");
640640
out->print_cr(" All log messages are written to an intermediate buffer first and will then be flushed"
641641
" to the corresponding log outputs by a standalone thread. Write operations at logsites are"
642642
" guaranteed non-blocking.");
643+
out->print_cr(" A mode, either 'drop' or 'stall', may be provided. If 'drop' is provided then"
644+
" messages will be dropped if there is no room in the intermediate buffer."
645+
" If 'stall' is provided then the log operation will wait for room to be made by the output thread, without dropping any messages."
646+
" The default mode is 'drop'.");
647+
643648
out->cr();
644649

645650
out->print_cr("Some examples:");
@@ -715,4 +720,20 @@ void LogConfiguration::notify_update_listeners() {
715720
}
716721
}
717722

718-
bool LogConfiguration::_async_mode = false;
723+
LogConfiguration::AsyncMode LogConfiguration::_async_mode = AsyncMode::Off;
724+
725+
bool LogConfiguration::parse_async_argument(const char* async_tail) {
726+
bool ret = true;
727+
if (*async_tail == '\0') {
728+
// Default is to drop.
729+
LogConfiguration::set_async_mode(LogConfiguration::AsyncMode::Drop);
730+
} else if (strcmp(async_tail, ":stall") == 0) {
731+
LogConfiguration::set_async_mode(LogConfiguration::AsyncMode::Stall);
732+
} else if (strcmp(async_tail, ":drop") == 0) {
733+
LogConfiguration::set_async_mode(LogConfiguration::AsyncMode::Drop);
734+
} else {
735+
// User provided unknown async option
736+
ret = false;
737+
}
738+
return ret;
739+
}

0 commit comments

Comments
 (0)