| /* | |
| * 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(); | |
| } | |
| } |