| /* |
| * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. |
| * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| #include "logging/logAsyncWriter.hpp" |
| #include "logging/logConfiguration.hpp" |
| #include "logging/logFileOutput.hpp" |
| #include "logging/logFileStreamOutput.hpp" |
| #include "memory/allocation.hpp" |
| #include "memory/resourceArea.hpp" |
| #include "runtime/atomic.hpp" |
| |
| class AsyncLogWriter::Locker : public StackObj { |
| Thread*& _holder; |
| PlatformMonitor& _lock; |
| |
| public: |
| Locker(Thread*& holder, PlatformMonitor& lock) |
| : _holder(holder), |
| _lock(lock) { |
| _lock.lock(); |
| _holder = Thread::current_or_null(); |
| } |
| |
| ~Locker() { |
| assert(_holder == Thread::current_or_null(), "must be"); |
| _holder = nullptr; |
| _lock.unlock(); |
| } |
| |
| void notify() { |
| _lock.notify(); |
| } |
| |
| void wait() { |
| Thread* saved_holder = _holder; |
| _holder = nullptr; |
| _lock.wait(0 /* no timeout */); |
| _holder = saved_holder; |
| } |
| }; |
| |
| class AsyncLogWriter::ProducerLocker : public Locker { |
| static Thread* _holder; |
| public: |
| static Thread* current_holder() { return _holder; } |
| ProducerLocker() : Locker(_holder, _instance->_producer_lock) {} |
| }; |
| |
| class AsyncLogWriter::ConsumerLocker : public Locker { |
| static Thread* _holder; |
| public: |
| static Thread* current_holder() { return _holder; } |
| ConsumerLocker() : Locker(_holder, _instance->_consumer_lock) {} |
| }; |
| |
| Thread* AsyncLogWriter::ProducerLocker::_holder = nullptr; |
| Thread* AsyncLogWriter::ConsumerLocker::_holder = nullptr; |
| |
| // LogDecorator::None applies to 'constant initialization' because of its constexpr constructor. |
| const LogDecorations& AsyncLogWriter::None = LogDecorations(LogLevel::Warning, LogTagSetMapping<LogTag::__NO_TAG>::tagset(), |
| LogDecorators::None); |
| |
| bool AsyncLogWriter::Buffer::push_back(LogFileStreamOutput* output, const LogDecorations& decorations, const char* msg, const size_t msg_len) { |
| const size_t len = msg_len; |
| const size_t sz = Message::calc_size(len); |
| const bool is_token = output == nullptr; |
| // Always leave headroom for the flush token. Pushing a token must succeed. |
| const size_t headroom = (!is_token) ? Message::calc_size(0) : 0; |
| |
| if (_pos + sz <= (_capacity - headroom)) { |
| new(_buf + _pos) Message(output, decorations, msg, len); |
| _pos += sz; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void AsyncLogWriter::Buffer::push_flush_token() { |
| bool result = push_back(nullptr, AsyncLogWriter::None, "", 0); |
| assert(result, "fail to enqueue the flush token."); |
| } |
| |
| void AsyncLogWriter::enqueue_locked(LogFileStreamOutput* output, const LogDecorations& decorations, const char* msg) { |
| // To save space and streamline execution, we just ignore null message. |
| // client should use "" instead. |
| assert(msg != nullptr, "enqueuing a null message!"); |
| |
| size_t msg_len = strlen(msg); |
| void* stalled_message = nullptr; |
| { |
| ConsumerLocker clocker; |
| if (_buffer->push_back(output, decorations, msg, msg_len)) { |
| _data_available = true; |
| clocker.notify(); |
| return; |
| } |
| |
| if (LogConfiguration::async_mode() == LogConfiguration::AsyncMode::Stall) { |
| size_t size = Message::calc_size(msg_len); |
| stalled_message = os::malloc(size, mtLogging); |
| if (stalled_message == nullptr) { |
| // Out of memory. We bail without any notice. |
| // Some other part of the system will probably fail later. |
| return; |
| } |
| _stalled_message = new (stalled_message) Message(output, decorations, msg, msg_len); |
| _data_available = true; |
| clocker.notify(); |
| // Note: we still hold the producer lock so cannot race against other threads trying to log a message |
| while (_stalled_message != nullptr) { |
| clocker.wait(); |
| } |
| } else { |
| bool p_created; |
| uint32_t* counter = _stats.put_if_absent(output, 0, &p_created); |
| *counter = *counter + 1; |
| } |
| } // ConsumerLocker out of scope |
| os::free(stalled_message); |
| } |
| |
| // This function checks for cases where continuing with asynchronous logging may lead to stability issues, such as a deadlock. |
| // If this returns false then we give up on logging asynchronously and do so synchronously instead. |
| bool AsyncLogWriter::is_enqueue_allowed() { |
| AsyncLogWriter* alw = AsyncLogWriter::instance(); |
| Thread* holding_thread = AsyncLogWriter::ProducerLocker::current_holder(); |
| Thread* this_thread = Thread::current_or_null(); |
| if (this_thread == nullptr) { |
| // The current thread is unattached. |
| return false; |
| } |
| |
| if (holding_thread == this_thread) { |
| // A thread, while enqueuing a message, has attempted to log something. |
| // Do not log while holding the Async log lock. |
| // Try to catch possible occurrences in debug builds. |
| #ifdef ASSERT |
| if (!TestingAsyncLoggingDeathTestNoCrash) { |
| ShouldNotReachHere(); |
| } |
| #endif // ASSERT |
| |
| return false; |
| } |
| |
| if (alw == nullptr) { |
| // There is no AsyncLogWriter instance yet. |
| return false; |
| } |
| |
| if (this_thread == alw) { |
| // The async log producer is attempting to log, leading to recursive logging. |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool AsyncLogWriter::enqueue(LogFileStreamOutput& output, const LogDecorations& decorations, const char* msg) { |
| if (!is_enqueue_allowed()) { |
| return false; |
| } |
| |
| ProducerLocker plocker; |
| |
| #ifdef ASSERT |
| if (TestingAsyncLoggingDeathTest || TestingAsyncLoggingDeathTestNoCrash) { |
| log_debug(deathtest)("Induce a recursive log for testing"); |
| } |
| #endif // ASSERT |
| |
| AsyncLogWriter::instance()->enqueue_locked(&output, decorations, msg); |
| return true; |
| } |
| |
| // LogMessageBuffer consists of a multiple-part/multiple-line message. |
| // The lock here guarantees its integrity. |
| bool AsyncLogWriter::enqueue(LogFileStreamOutput& output, LogMessageBuffer::Iterator msg_iterator) { |
| if (!is_enqueue_allowed()) { |
| return false; |
| } |
| |
| // If we get here we know the AsyncLogWriter is initialized. |
| ProducerLocker plocker; |
| for (; !msg_iterator.is_at_end(); msg_iterator++) { |
| AsyncLogWriter::instance()->enqueue_locked(&output, msg_iterator.decorations(), msg_iterator.message()); |
| } |
| return true; |
| } |
| |
| AsyncLogWriter::AsyncLogWriter() |
| : _flush_sem(0), |
| _producer_lock(), |
| _consumer_lock(), |
| _data_available(false), |
| _initialized(false), |
| _stats(), |
| _stalled_message(nullptr) { |
| |
| size_t size = AsyncLogBufferSize / 2; |
| _buffer = new Buffer(size); |
| _buffer_staging = new Buffer(size); |
| log_info(logging)("AsyncLogBuffer estimates memory use: %zu bytes", size * 2); |
| if (os::create_thread(this, os::asynclog_thread)) { |
| _initialized = true; |
| } else { |
| log_warning(logging, thread)("AsyncLogging failed to create thread. Falling back to synchronous logging."); |
| } |
| } |
| |
| bool AsyncLogWriter::write(AsyncLogMap<AnyObj::RESOURCE_AREA>& snapshot) { |
| int req = 0; |
| auto it = _buffer_staging->iterator(); |
| while (it.hasNext()) { |
| const Message* e = it.next(); |
| |
| if (!e->is_token()){ |
| e->output()->write_blocking(e->decorations(), e->message()); |
| } else { |
| // This is a flush token. Record that we found it and then |
| // signal the flushing thread after the loop. |
| req++; |
| } |
| } |
| |
| LogDecorations decorations(LogLevel::Warning, LogTagSetMapping<LogTag::__NO_TAG>::tagset(), |
| LogDecorators::All); |
| snapshot.iterate([&](LogFileStreamOutput* output, uint32_t& counter) { |
| if (counter > 0) { |
| stringStream ss; |
| ss.print(UINT32_FORMAT_W(6) " messages dropped due to async logging", counter); |
| output->write_blocking(decorations, ss.freeze()); |
| } |
| return true; |
| }); |
| |
| if (req > 0) { |
| assert(req == 1, "Only one token is allowed in queue. AsyncLogWriter::flush() is NOT MT-safe!"); |
| return true; |
| } |
| return false; |
| } |
| |
| void AsyncLogWriter::run() { |
| while (true) { |
| ResourceMark rm; |
| AsyncLogMap<AnyObj::RESOURCE_AREA> snapshot; |
| { |
| ConsumerLocker clocker; |
| while (!_data_available) { |
| clocker.wait(); |
| } |
| |
| // Only doing a swap and statistics under the lock to |
| // guarantee that I/O jobs don't block logsites. |
| _buffer_staging->reset(); |
| swap(_buffer, _buffer_staging); |
| |
| // move counters to snapshot and reset them. |
| _stats.iterate([&] (LogFileStreamOutput* output, uint32_t& counter) { |
| if (counter > 0) { |
| bool created = snapshot.put(output, counter); |
| assert(created == true, "sanity check"); |
| counter = 0; |
| } |
| return true; |
| }); |
| _data_available = false; |
| } |
| |
| bool saw_flush_token = write(snapshot); |
| |
| // Any stalled message must be written *after* the buffer has been written. |
| // This is because we try hard to output messages in program-order. |
| if (_stalled_message != nullptr) { |
| assert(LogConfiguration::async_mode() == LogConfiguration::AsyncMode::Stall, "must be"); |
| ConsumerLocker clocker; |
| Message* m = (Message*)_stalled_message; |
| m->output()->write_blocking(m->decorations(), m->message()); |
| _stalled_message = nullptr; |
| clocker.notify(); |
| } |
| |
| if (saw_flush_token) { |
| _flush_sem.signal(1); |
| } |
| } |
| } |
| |
| AsyncLogWriter* AsyncLogWriter::_instance = nullptr; |
| |
| void AsyncLogWriter::initialize() { |
| if (!LogConfiguration::is_async_mode()) return; |
| |
| assert(_instance == nullptr, "initialize() should only be invoked once."); |
| |
| AsyncLogWriter* self = new AsyncLogWriter(); |
| if (self->_initialized) { |
| // We use LogOutputList's RCU counters to ensure all synchronous logsites have completed. |
| // After that, we publish the initalized _instance to readers. |
| // Then we start the AsyncLog Thread and it exclusively takes over all logging I/O. |
| for (LogTagSet* ts = LogTagSet::first(); ts != nullptr; ts = ts->next()) { |
| ts->wait_until_no_readers(); |
| } |
| Atomic::release_store_fence(&AsyncLogWriter::_instance, self); |
| os::start_thread(self); |
| log_debug(logging, thread)("Async logging thread started."); |
| } else { |
| delete self; |
| } |
| } |
| |
| AsyncLogWriter* AsyncLogWriter::instance() { |
| return _instance; |
| } |
| |
| // Inserts a flush token into the async output buffer and waits until the AsyncLog thread |
| // signals that it has seen it and completed all dequeued message processing. |
| // This method is not MT-safe in itself, but is guarded by another lock in the usual |
| // usecase - see the comments in the header file for more details. |
| void AsyncLogWriter::flush() { |
| if (_instance != nullptr) { |
| { |
| ProducerLocker plocker; |
| ConsumerLocker clocker; |
| // Push directly in-case we are at logical max capacity, as this must not get dropped. |
| _instance->_buffer->push_flush_token(); |
| _instance->_data_available = true; |
| clocker.notify(); |
| } |
| |
| _instance->_flush_sem.wait(); |
| } |
| } |