RFR: 8292989: Avoid dynamic memory in AsyncLogWriter [v4]
Thomas Stuefe
stuefe at openjdk.org
Sat Sep 10 05:23:40 UTC 2022
On Fri, 9 Sep 2022 22:23:05 GMT, Xin Liu <xliu at openjdk.org> wrote:
>> src/hotspot/share/logging/logAsyncWriter.cpp line 64:
>>
>>> 62: size_t sz = align_up(sizeof(Message) + len, sizeof(void*));
>>> 63:
>>> 64: if (_pos + sz <= _capacity || output == nullptr/*token*/) {
>>
>> I don't understand this. The push token thing is not zero-sized, so what happens if we go over capacity? Or, in other words, if we fill the buffer with push tokens only, won't we overwrite and crash?
>
> Currently, there's only one token, ie. the flushing token. We use it to implement "flushing" semantic.
>
> `AsyncLogWriter::flush` inserts it and is blocked until "flushing" is complete.
> Therefore, one queue can only have one token anytime. I reserve it in ctor so enqueuing of the token always succeeds. Unlike regular enqueuing, failure is not acceptable in this case because it will hang AsyncLogWriter thread.
>
> Previously, I have an object called 'Token'. Since now the ctor only works for in-place new, I can't construct it on c-heap or stack anymore. I use a tuple {nullptr, AsyncLogWriter::None, ""} instead. here is function.
>
> void AsyncLogWriter::Buffer::push_flush_token() {
> bool result = push_back(nullptr, AsyncLogWriter::None, "");
> assert(result, "fail to enqueue the flush token");
> }
>
>
> It's impossible to enqueue a non-token object from outside because the public interface `AsyncLogWriter::enqueue` only receives LogFileStreamOutput& as the 1st argument. There's no way to pass in null reference in C++.
Ah, I see. This is easy to miss. May I suggest a slight alteration that makes it easier to understand to the casual reader?
In Message, define a static function that calculates message byte size (the various definitions existing make me nervous, easy for them to drift apart):
// Calculate size for a prospective Message object depending on its message length
static constexpr size_t calc_size(int message_len) {
return align_up(sizeof(Message) + message_len + 1, sizeof(void*));
}
Use this in Message::size().
Get rid of TOKEN, and in the ctor, let _capacity be the real capacity. Instead, in push_back, do:
bool AsyncLogWriter::Buffer::push_back(LogFileStreamOutput* output, const LogDecorations& decorations, const char* msg) {
const size_t sz = Message::calc_size(strlen(msg));
const bool is_push_token = output == nullptr;
// Always leave headroom for the push token. Pushing the push token must always succeed.
const size_t headroom = (!is_push_token) ? Message::calc_size(0) : 0;
if (_pos + sz <= (_capacity - headroom)) {
new(_buf + _pos) Message(output, decorations, msg);
_pos += sz;
return true;
}
return false;
}
Now the magic is in exactly one place, and easy to understand with one glance. And we have one central place to calculate the message byte size.
-------------
PR: https://git.openjdk.org/jdk/pull/10092
More information about the hotspot-runtime-dev
mailing list