This is an archive of the discontinued LLVM Phabricator instance.

[lldb] Introduce the concept of a log handler (NFC)
ClosedPublic

Authored by JDevlieghere on Jun 15 2022, 5:12 PM.

Details

Summary

This patch introduces the concept of a log handlers. Log handlers allow customizing the way log output is emitted. The StreamCallback class tried to do something conceptually similar. The benefit of the log handler interface is that you don't need to conform to llvm's raw_ostream interface.

This patch is in preparation for a new kind of a new type of log handler.

Diff Detail

Event Timeline

JDevlieghere created this revision.Jun 15 2022, 5:12 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 15 2022, 5:12 PM
Herald added a subscriber: mgorny. · View Herald Transcript
JDevlieghere requested review of this revision.Jun 15 2022, 5:12 PM

Update unit tests

clayborg added inline comments.Jun 16 2022, 10:34 AM
lldb/source/Utility/Log.cpp
322–323

Since we are improving stuff here, should we add a mutex to each handler and use the handler specific mutex if needed?

323–325

Will this copy the string? The Emit() calls take a "std::string", maybe we should switch it to "const std::string &" to avoid any copies?

327–328

Ditto

JDevlieghere marked 3 inline comments as done.Jun 16 2022, 10:36 AM
JDevlieghere added inline comments.
lldb/source/Utility/Log.cpp
322–323

Yup, I think that makes sense

323–325

Yeah, was planning on std::move-ing everything but I already found a few cases where that doesn't work. This should indeed take a reference.

mib added inline comments.Jun 16 2022, 10:54 AM
lldb/source/Utility/Log.cpp
323–325

+1

clayborg added inline comments.Jun 16 2022, 11:05 AM
lldb/source/Utility/Log.cpp
323–325

The other option is to have the Emit functions use llvm::StringRef?:

virtual void Emit(llvm::StringRef s) = 0;
JDevlieghere marked 2 inline comments as done.
  • Make Emit take a StringRef
  • Move mutex in the log handler
JDevlieghere marked 2 inline comments as done.Jun 16 2022, 11:16 AM
clayborg added inline comments.Jun 16 2022, 11:23 AM
lldb/include/lldb/Utility/Log.h
54

Maybe we don't expose this mutex at all and we have a EmitThreadSafe(...) method?:

void EmitThreadSafe(llvm::StringRef message) {
  std::lock_guard<std::mutex> guard(m_mutex);
  Emit(message);
}
lldb/source/Utility/Log.cpp
323–324

If we like the idea of EmitThreadSafe then the code can look like this

325

Do we ever not call Flush() after a Emit() call? If so do we even need the flush call, or should we just leave it up to the specific implementation to do what it wants in Emit(...)?

328

Do we ever not call Flush() after a Emit() call? If so do we even need the flush call, or should we just leave it up to the specific implementation to do what it wants in Emit(...)?

JDevlieghere marked 4 inline comments as done.
  • Add EmitThreadSafe
  • Remove Flush
This revision is now accepted and ready to land.Jun 16 2022, 12:57 PM
This revision was automatically updated to reflect the committed changes.
Herald added a project: Restricted Project. · View Herald TranscriptJun 16 2022, 1:34 PM

I think we should talk about the threadsafe flag. The reason that the non-threadsafe mode is safe now is because we're using unbuffered streams, where each stream operation will map to a single write syscall (which will then be serialized by the OS somehow). And since we pre-format the log messages into a temporary buffer, we shouldn't even get overlapping messages in the output. In fact, I would say that, before this, the threadsafe mode was mostly useless.

All of that goes out the window once you start doing arbitrary logic in place of log writing. As they're implemented now, neither BufferedLogHandler nor RotatingLogHandler are thread-safe. (Making the rotating handler thread safe (without locks) should be relatively easy -- assuming we're willing to accept races between printing messages, and new messages coming in -- but I don't think that the Buffered handler can be made safe without locking at least the flushing part). An it's not just that the messages may be slightly wonky. I think it wouldn't be hard to crash the process by spewing log messages from multiple threads (and we're pretty good at spewing :P).

So, what I'd recommend is to drop the thread-safe flag, and make the locking strategy the responsibility of the individual log handler. When writing to an unbuffered stream, we wouldn't need any locks at all. The circular handler could either lock into that or, if you're into that sort of thing, use atomics to make enqueueing lock-free. The buffered handler could either use a buffered ostream, wrapped in a mutex, or do something similar to the circular one, while locking only the flushing code.

I think we should talk about the threadsafe flag. The reason that the non-threadsafe mode is safe now is because we're using unbuffered streams, where each stream operation will map to a single write syscall (which will then be serialized by the OS somehow). And since we pre-format the log messages into a temporary buffer, we shouldn't even get overlapping messages in the output. In fact, I would say that, before this, the threadsafe mode was mostly useless.

All of that goes out the window once you start doing arbitrary logic in place of log writing. As they're implemented now, neither BufferedLogHandler nor RotatingLogHandler are thread-safe. (Making the rotating handler thread safe (without locks) should be relatively easy -- assuming we're willing to accept races between printing messages, and new messages coming in -- but I don't think that the Buffered handler can be made safe without locking at least the flushing part). An it's not just that the messages may be slightly wonky. I think it wouldn't be hard to crash the process by spewing log messages from multiple threads (and we're pretty good at spewing :P).

So, what I'd recommend is to drop the thread-safe flag, and make the locking strategy the responsibility of the individual log handler. When writing to an unbuffered stream, we wouldn't need any locks at all. The circular handler could either lock into that or, if you're into that sort of thing, use atomics to make enqueueing lock-free. The buffered handler could either use a buffered ostream, wrapped in a mutex, or do something similar to the circular one, while locking only the flushing code.

Yup that makes sense to me. I'll put up a patch for that tomorrow.

lldb/unittests/Core/CMakeLists.txt