I'm experimenting with C++/Swift interop and stumbled upon a problem. But first of all some code.
The Swift side:
import Foundation
import OSLog
// Struct to hold log entry data
public struct LogEntry {
public let timestamp: TimeInterval
public let message: String
public let process: String
public let pid: Int
public let level: String
}
// Function to fetch logs
public func fetchLogs() -> [LogEntry] {
var logEntriesList: [LogEntry] = []
guard let logStore = try? OSLogStore(scope: .system) else {
return logEntriesList
}
do {
let lastThirtySeconds = logStore.position(date: Date().addingTimeInterval(-30))
let logEntries = try logStore.getEntries(at: lastThirtySeconds)
for entry in logEntries {
if let logEntry = entry as? OSLogEntryLog {
let entryData = LogEntry(
timestamp: logEntry.date.timeIntervalSince1970,
message: logEntry.composedMessage,
process: logEntry.process,
pid: Int(logEntry.processIdentifier),
level: "\(logEntry.level)"
)
logEntriesList.append(entryData)
}
}
} catch {
return []
}
return logEntriesList
}
My Swift code retrieves log entries using the OSLog framework under macOS. I wrote a function in Swift returning an array of entries consumed on the C++ side. So far, so good. Everything works as expected except one thing.
The line
const std::string msg = log.getMessage();
causes the whole process to max out at about 100% CPU usage after successfully iterating over a few dozen entries (see the attached screenshot). Moreover, the process consumes more and more RAM and won't exit until manually forced to.
My C++ a bit rusty, I don't remember how static_cast differs from old-C-style cast (if differs at all, and that’s why I can’t remember), but Swift docs on the matter suggest conversion in that way to std::string:
So this happens not for all messages? Maybe that's something inside the string that causes this behaviour? Seems like it stuck in iteration loop. Have you tried to look at the string that causes this?
It does not look to be especially long or malformed. The only exception could be the German "ä," although setting the variable "msg" to "äää" or "ä" did not trigger the above-mentioned behavior.
extension std.string {
/// Creates a C++ string having the same content as the given Swift string.
///
/// - Complexity: O(*n*), where *n* is the number of UTF-8 code units in the
/// Swift string.
public init(_ string: String) {
self.init()
let utf8 = string.utf8
self.reserve(utf8.count)
for char in utf8 {
self.push_back(value_type(bitPattern: char))
}
}
(Also, it seems like this could be made a lot faster for contiguous strings by using the std::basic_string constructor that takes a count and a pointer to CharT. Filed as issue #75110.)
Hm, I’m starting to wonder if something is throwing an exception and corrupting the Swift iterator’s state. Can you try adding a breakpoint that triggers on C++ exception throw?
I added a breakpoint that triggers on C++ exceptions, but none was thrown.
As a side note, I let the process run for two minutes to show the memory usage in more detail as (maybe) another hint. See this screenshot:
As it is getting late here in Germany, I have to apologize for leaving for the moment. I will definitely be back tomorrow, maybe (hopefully) with an explanation.
In the meantime, I would like to really thank everybody here for your help. The quick responses I got, as well as the thorough guidance, are outstanding!
I investigated the problem further today but could not find the exact issue. As it seems not to be too trivial to find, I opened up a ticket at Apple. Maybe they can shed some light on it.
I profiled the process with instruments, and as @ksluder suggested, there seems to be a problem with UTF8View:
String you are receiving seems to be not exactly "native" if I understood correctly call tree, but bridged from CoreFoundation... Maybe you can try to convert it to pure Swift string before passing to LogEntry (maybe constructing it char by char, as assumption)? But if any, that seems like a bug after all.