Skip to content

Commit 4ae1606

Browse files
Vladislav AlekseevAvito iOSBot
authored andcommitted
MBS-13416: linux
GitOrigin-RevId: f710ba47f2febd7a09e4736f06000fa159590602
1 parent 0511588 commit 4ae1606

30 files changed

+398
-297
lines changed

PackageGenerator/Sources/PackageGenerator/ImportsParser.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ public final class ImportsParser {
55

66
public init() throws {
77
// https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_import-declaration
8-
let anyWhitespace = #"\s*"#
98
let whitespace = #"\s+"#
109
let attributesRegex = "@testable"
1110
let importKindRegex = "(?:typealias|struct|class|enum|protocol|let|var|func)"

Sources/FileSystem/Files/RealPathProvider/RealpathProviderImpl.swift

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1+
#if canImport(Darwin)
12
import Darwin
3+
#endif
4+
#if canImport(Glibc)
5+
import Glibc
6+
#endif
7+
28
import PathLib
39

410
/// Resolves all symbolic links, extra "/" characters, and references to /./
@@ -20,7 +26,7 @@ public final class RealpathProviderImpl: RealpathProvider {
2026
}
2127

2228
public func realpath(path: AbsolutePath) throws -> AbsolutePath {
23-
guard let result = Darwin.realpath(path.pathString, nil) else {
29+
guard let result = systemRealpath(path.pathString) else {
2430
throw RealpathError(
2531
errno: errno,
2632
path: path
@@ -31,4 +37,14 @@ public final class RealpathProviderImpl: RealpathProvider {
3137

3238
return AbsolutePath(String(cString: result))
3339
}
40+
41+
private func systemRealpath(_ path: String) -> UnsafeMutablePointer<CChar>! {
42+
#if canImport(Darwin)
43+
return Darwin.realpath(path, nil)
44+
#elseif canImport(Glibc)
45+
return Glibc.realpath(path, nil)
46+
#else
47+
return nil
48+
#endif
49+
}
3450
}

Sources/Graphite/GraphiteMetricHandlerImpl.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ public final class GraphiteMetricHandlerImpl: GraphiteMetricHandler {
1717

1818
let streamReopener = StreamReopener(maximumAttemptsToReopenStream: 10)
1919

20-
outputStream = EasyOutputStream(
21-
outputStreamProvider: NetworkSocketOutputStreamProvider(
20+
#if os(macOS) || os(iOS) || os(tvOS)
21+
outputStream = AppleEasyOutputStream(
22+
outputStreamProvider: AppleNetworkSocketOutputStreamProvider(
2223
host: graphiteSocketAddress.host,
2324
port: graphiteSocketAddress.port.value
2425
),
@@ -29,6 +30,9 @@ public final class GraphiteMetricHandlerImpl: GraphiteMetricHandler {
2930
streamReopener.attemptToReopenStream(stream: stream)
3031
}
3132
)
33+
#else
34+
outputStream = LinuxEasyOutputStream()
35+
#endif
3236

3337
streamReopener.streamHasBeenOpened()
3438
try outputStream.open()
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import AtomicModels
2+
import Foundation
3+
4+
#if os(macOS) || os(iOS) || os(tvOS)
5+
6+
// swiftlint:disable async
7+
public final class AppleEasyOutputStream: NSObject, EasyOutputStream, StreamDelegate {
8+
9+
public typealias ErrorHandler = (EasyOutputStream, EasyOutputStreamError) -> ()
10+
public typealias StreamEndHandler = (EasyOutputStream) -> ()
11+
12+
/// Instance that can create output streams upon request.
13+
private let outputStreamProvider: OutputStreamProvider
14+
15+
/// Runtime stream error handler.
16+
private let errorHandler: ErrorHandler
17+
18+
/// Stream end handler is called when stream concludes writing data. After this stream will be closed.
19+
private let streamEndHandler: StreamEndHandler
20+
21+
/// Queue used to call error handler.
22+
private let handlerQueue = DispatchQueue(label: "EasyOutputStream.errorQueue")
23+
24+
/// Indicator if stream is accepting new data or closing because of waitAndClose()
25+
private let acceptsNewData = AtomicValue<Bool>(false)
26+
27+
/// A size of single write attemp.
28+
private let batchSize: Int
29+
30+
/// An indication that we should send new data immediately to output stream instead of waiting for StreamDelegate event.
31+
/// Happens if we have zero data to write to output stream. Then, to wake up StreamDelegate flow, we must send
32+
/// data to stream directly.
33+
private var needsToSendDataToStreamDirectly = AtomicValue<Bool>(false)
34+
35+
/// Thread that owns a run loop which is used by output stream.
36+
private var thread: Thread?
37+
38+
/// Buffer is used to enqueue data to be sent into output stream.
39+
private let buffer = AtomicCollection<Data>(Data())
40+
41+
public init(
42+
outputStreamProvider: OutputStreamProvider,
43+
batchSize: Int = 1024,
44+
errorHandler: @escaping ErrorHandler,
45+
streamEndHandler: @escaping StreamEndHandler)
46+
{
47+
self.outputStreamProvider = outputStreamProvider
48+
self.batchSize = batchSize
49+
self.errorHandler = errorHandler
50+
self.streamEndHandler = streamEndHandler
51+
}
52+
53+
/// Closes previously opened streams, and opens a new stream.
54+
public func open() throws {
55+
wakeUpAndCancelThread()
56+
57+
let outputStream = try outputStreamProvider.createOutputStream()
58+
thread = Thread(target: self, selector: #selector(handleStream(outputStream:)), object: outputStream)
59+
thread?.name = "EasyOutputStream.thread"
60+
thread?.start()
61+
62+
acceptsNewData.set(true)
63+
}
64+
65+
/// Immediately closes the output stream and erases buffer.
66+
public func close() {
67+
acceptsNewData.set(false)
68+
buffer.set(Data())
69+
wakeUpAndCancelThread()
70+
}
71+
72+
/// Waits given time to deliver buffered data and then closes the output stream.
73+
/// - Parameter timeout: Time for writing all enqueued data.
74+
/// - Returns: `true` if torn down within limit, `false` on timeout.
75+
public func waitAndClose(timeout: TimeInterval) -> TearDownResult {
76+
acceptsNewData.set(false)
77+
wakeUpThreadsRunloop()
78+
79+
let result = buffer.waitWhen(
80+
count: 0,
81+
before: Date().addingTimeInterval(timeout)
82+
)
83+
84+
close()
85+
if result {
86+
return .successfullyFlushedInTime
87+
} else {
88+
return .flushTimeout
89+
}
90+
}
91+
92+
/// Enqueues data to be sent via stream.
93+
public func enqueueWrite(data: Data) throws {
94+
guard acceptsNewData.currentValue() else {
95+
throw EasyOutputStreamError.streamClosed
96+
}
97+
98+
guard !data.isEmpty else { return }
99+
100+
buffer.withExclusiveAccess { bytes in
101+
bytes.append(data)
102+
}
103+
104+
wakeUpThreadsRunloop()
105+
}
106+
107+
// MARK: - Stream handing
108+
109+
@objc private func handleStream(outputStream: OutputStream) {
110+
outputStream.delegate = self
111+
outputStream.schedule(in: RunLoop.current, forMode: RunLoop.Mode.default)
112+
outputStream.open()
113+
114+
while !Thread.current.isCancelled {
115+
RunLoop.current.run(mode: RunLoop.Mode.default, before: Date.distantFuture)
116+
writeDataIfEnqueuedOutOfDelegateEventIfNeeded(outputStream: outputStream)
117+
}
118+
119+
outputStream.close()
120+
outputStream.remove(from: RunLoop.current, forMode: RunLoop.Mode.default)
121+
outputStream.delegate = nil
122+
}
123+
124+
public func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
125+
guard let outputStream = aStream as? OutputStream else {
126+
fatalError("Unexpected stream type: \(aStream)")
127+
}
128+
129+
switch eventCode {
130+
case .openCompleted:
131+
break
132+
case .hasBytesAvailable:
133+
break
134+
case .hasSpaceAvailable:
135+
writeDataOnDelegateEvent(outputStream: outputStream)
136+
case .errorOccurred:
137+
if let streamError = aStream.streamError {
138+
handlerQueue.async {
139+
self.errorHandler(self, EasyOutputStreamError.streamError(streamError))
140+
}
141+
}
142+
wakeUpAndCancelThread()
143+
case .endEncountered:
144+
handlerQueue.async {
145+
self.streamEndHandler(self)
146+
}
147+
wakeUpAndCancelThread()
148+
default:
149+
break
150+
}
151+
}
152+
153+
/// Writes data and returns a number of bytes written. Should be called when stream has some space available.
154+
@discardableResult
155+
private func writeBufferedData(outputStream: OutputStream, numberOfBytes: Int) -> Int {
156+
return buffer.withExclusiveAccess { data in
157+
let bytesWritten: Int = data.withUnsafeBytes { bufferPointer -> Int in
158+
let bytes = bufferPointer.bindMemory(to: UInt8.self)
159+
if let baseAddress = bytes.baseAddress {
160+
return outputStream.write(baseAddress, maxLength: min(numberOfBytes, data.count))
161+
}
162+
return 0
163+
}
164+
if bytesWritten > 0 {
165+
data = data.dropFirst(bytesWritten)
166+
}
167+
return bytesWritten
168+
}
169+
}
170+
171+
private func writeDataOnDelegateEvent(outputStream: OutputStream) {
172+
if buffer.currentValue().isEmpty {
173+
needsToSendDataToStreamDirectly.set(true)
174+
} else {
175+
writeBufferedData(outputStream: outputStream, numberOfBytes: batchSize)
176+
}
177+
}
178+
179+
private func writeDataIfEnqueuedOutOfDelegateEventIfNeeded(outputStream: OutputStream) {
180+
guard !buffer.currentValue().isEmpty else { return }
181+
182+
if needsToSendDataToStreamDirectly.currentValue() {
183+
if outputStream.hasSpaceAvailable {
184+
writeBufferedData(outputStream: outputStream, numberOfBytes: 1)
185+
} else {
186+
errorHandler(self, EasyOutputStreamError.streamHasNoSpaceAvailable)
187+
}
188+
needsToSendDataToStreamDirectly.set(false)
189+
}
190+
}
191+
192+
// It's important to wake up and cancel thread simultaneously
193+
// (there was a crash before it was synchronized)
194+
private func wakeUpAndCancelThread() {
195+
if let thread = thread {
196+
self.perform(
197+
#selector(cancelThread),
198+
on: thread,
199+
with: nil,
200+
waitUntilDone: false
201+
)
202+
}
203+
}
204+
205+
@objc private func cancelThread() {
206+
thread?.cancel()
207+
thread = nil
208+
}
209+
210+
// MARK: - RunLoop Awaking
211+
212+
private func wakeUpThreadsRunloop() {
213+
if let thread = thread {
214+
self.perform(
215+
#selector(wakeUpThreadRunloop_onThread),
216+
on: thread,
217+
with: nil,
218+
waitUntilDone: false
219+
)
220+
}
221+
}
222+
223+
@objc private func wakeUpThreadRunloop_onThread() {
224+
// this method exists just to be able to wake up the runloop
225+
}
226+
}
227+
228+
#endif

Sources/IO/NetworkSocketOutputStreamProvider.swift renamed to Sources/IO/AppleNetworkSocketOutputStreamProvider.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Foundation
22

3-
public final class NetworkSocketOutputStreamProvider: OutputStreamProvider {
3+
#if os(macOS) || os(iOS) || os(tvOS)
4+
public final class AppleNetworkSocketOutputStreamProvider: OutputStreamProvider {
45
private let host: String
56
private let port: Int
67

@@ -34,3 +35,4 @@ public final class NetworkSocketOutputStreamProvider: OutputStreamProvider {
3435
return outputStream
3536
}
3637
}
38+
#endif

0 commit comments

Comments
 (0)