// This file is part of Background Music. // // Background Music is free software: you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation, either version 2 of the // License, or (at your option) any later version. // // Background Music 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 for more details. // // You should have received a copy of the GNU General Public License // along with Background Music. If not, see . // // VCPlayThroughRTLogger.h // VCApp // // Copyright © 2020 Kyle Neideck // // A real-time safe logger for VCPlayThrough. The messages are logged asynchronously by a // non-realtime thread. // // For the sake of simplicity, this class is very closely coupled with VCPlayThrough and its // methods make assumptions about where they will be called. Also, if the same logging method is // called multiple times before the logging thread next checks for messages, it will only log the // message for one of those calls and ignore the others. // // This class's methods are real-time safe in that they return in a bounded amount of time and we // think they're probably fast enough that the callers won't miss their deadlines, but we don't try // to guarantee it. Some of them should only be called in unusual cases where it's worth increasing // the risk of a thread missing its deadline. // #ifndef VCApp__VCPlayThroughRTLogger #define VCApp__VCPlayThroughRTLogger // PublicUtility Includes #include "CARingBuffer.h" // STL Includes #include // System Includes #include #include #pragma clang assume_nonnull begin class VCPlayThroughRTLogger { #pragma mark Construction/Destruction public: VCPlayThroughRTLogger(); ~VCPlayThroughRTLogger(); VCPlayThroughRTLogger(const VCPlayThroughRTLogger&) = delete; VCPlayThroughRTLogger& operator=( const VCPlayThroughRTLogger&) = delete; private: static semaphore_t CreateSemaphore(); #pragma mark Log Messages public: /*! For VCPlayThrough::ReleaseThreadsWaitingForOutputToStart. */ void LogReleasingWaitingThreads(); /*! For VCPlayThrough::ReleaseThreadsWaitingForOutputToStart. */ void LogIfMachError_ReleaseWaitingThreadsSignal(mach_error_t inError); /*! For VCPlayThrough::OutputDeviceIOProc. Not thread-safe. */ void LogIfDroppedFrames(Float64 inFirstInputSampleTime, Float64 inLastInputSampleTime); /*! For VCPlayThrough::OutputDeviceIOProc. Not thread-safe. */ void LogNoSamplesReady(CARingBuffer::SampleTime inLastInputSampleTime, CARingBuffer::SampleTime inReadHeadSampleTime, Float64 inInToOutSampleOffset); /*! For VCPlayThrough::UpdateIOProcState. Not thread-safe. */ void LogExceptionStoppingIOProc(const char* inCallerName) { LogExceptionStoppingIOProc(inCallerName, noErr, false); } /*! For VCPlayThrough::UpdateIOProcState. Not thread-safe. */ void LogExceptionStoppingIOProc(const char* inCallerName, OSStatus inError) { LogExceptionStoppingIOProc(inCallerName, inError, true); } private: void LogExceptionStoppingIOProc(const char* inCallerName, OSStatus inError, bool inErrorKnown); public: /*! For VCPlayThrough::UpdateIOProcState. Not thread-safe. */ void LogUnexpectedIOStateAfterStopping(const char* inCallerName, int inIOState); /*! For VCPlayThrough::InputDeviceIOProc and VCPlayThrough::OutputDeviceIOProc. */ void LogRingBufferUnavailable(const char* inCallerName, bool inGotLock); /*! For VCPlayThrough::OutputDeviceIOProc. */ void LogIfRingBufferError_Fetch(CARingBufferError inError) { LogIfRingBufferError(inError, mRingBufferFetchError); } /*! For VCPlayThrough::InputDeviceIOProc. */ void LogIfRingBufferError_Store(CARingBufferError inError) { LogIfRingBufferError(inError, mRingBufferStoreError); } private: void LogIfRingBufferError(CARingBufferError inError, std::atomic& outError); template void LogAsync(T& inMessageData, F&& inStoreMessageData); // Wrapper methods used to mock out the logging for unit tests. void LogSync_Warning(const char* inFormat, ...) __printflike(2, 3); void LogSync_Error(const char* inFormat, ...) __printflike(2, 3); #pragma mark Logging Thread private: void WakeLoggingThread(); void LogMessages(); void LogSync_ReleasingWaitingThreads(); void LogSync_ReleaseWaitingThreadsSignalError(); void LogSync_DroppedFrames(); void LogSync_NoSamplesReady(); void LogSync_ExceptionStoppingIOProc(); void LogSync_UnexpectedIOStateAfterStopping(); void LogSync_RingBufferUnavailable(); void LogSync_RingBufferError( std::atomic& ioRingBufferError, const char* inMethodName); // The entry point of the logging thread (mLoggingThread). static void* __nullable LoggingThreadEntry(VCPlayThroughRTLogger* inRefCon); #if VC_UnitTest #pragma mark Test Helpers public: /*! * @return True if the logger thread finished logging the requested messages. False if it still * had messages to log after 5 seconds. */ bool WaitUntilLoggerThreadIdle(); #endif /* VC_UnitTest */ private: // For VCPlayThrough::ReleaseThreadsWaitingForOutputToStart std::atomic mLogReleasingWaitingThreadsMsg { false }; std::atomic mReleaseWaitingThreadsSignalError { KERN_SUCCESS }; // For VCPlayThrough::InputDeviceIOProc and VCPlayThrough::OutputDeviceIOProc struct { Float64 firstInputSampleTime; Float64 lastInputSampleTime; std::atomic shouldLogMessage { false }; } mDroppedFrames; struct { CARingBuffer::SampleTime lastInputSampleTime; CARingBuffer::SampleTime readHeadSampleTime; Float64 inToOutSampleOffset; std::atomic shouldLogMessage { false }; } mNoSamplesReady; struct { const char* callerName; bool gotLock; std::atomic shouldLogMessage { false }; } mRingBufferUnavailable; // For VCPlayThrough::UpdateIOProcState struct { const char* callerName; int ioState; std::atomic shouldLogMessage { false }; } mUnexpectedIOStateAfterStopping; struct { const char* callerName; OSStatus error; bool errorKnown; // If false, we didn't get an error code from the exception. std::atomic shouldLogMessage { false }; } mExceptionStoppingIOProc; // For VCPlayThrough::OutputDeviceIOProc std::atomic mRingBufferStoreError { kCARingBufferError_OK }; // For VCPlayThrough::InputDeviceIOProc. std::atomic mRingBufferFetchError { kCARingBufferError_OK }; // Signalled to wake up the mLoggingThread when it has messages to log. semaphore_t mWakeUpLoggingThreadSemaphore; std::atomic mLoggingThreadShouldExit { false }; // The thread that actually logs the messages. std::thread mLoggingThread; #if VC_UnitTest public: // Tests normally crash (abort) if LogError is called. This flag lets us test the code that // would otherwise call LogError. bool mContinueOnErrorLogged { false }; int mNumDebugMessagesLogged { 0 }; int mNumWarningMessagesLogged { 0 }; int mNumErrorMessagesLogged { 0 }; #endif /* VC_UnitTest */ }; #pragma clang assume_nonnull end #endif /* VCApp__VCPlayThroughRTLogger */