Make sure nothing other than the UI holds a reference to the connection. This way, the connection should drop when the user switches to another destination. However, I am seeing a crash if the user switches to another destination while connected and listening.

This commit is contained in:
Jeremy Rand 2022-03-24 00:03:35 -04:00
parent b732007d68
commit 109213a577
7 changed files with 145 additions and 63 deletions

View File

@ -17,6 +17,21 @@
</dict>
<key>SuppressBuildableAutocreation</key>
<dict>
<key>9D5155EE26A1EF7B0075EBC7</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>9D5155FF26A1EF7C0075EBC7</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>9D51560A26A1EF7C0075EBC7</key>
<dict>
<key>primary</key>
<true/>
</dict>
<key>9D62FC5F27C494D700AEE01F</key>
<dict>
<key>primary</key>

View File

@ -123,64 +123,61 @@ class GSConnection : ObservableObject {
logger.debug("Connected to \(self.destination)")
}
private func doConnect() {
logger.debug("Attempting to connect to \(self.destination)")
client = TCPClient(address: destination, port: Int32(GSConnection.port))
guard let client = client else {
mainQueue.addOperation { self.connectionFailed() }
return
}
switch client.connect(timeout: 10) {
case .success:
mainQueue.addOperation { self.connectionSuccessful() }
case .failure(let error):
client.close()
self.client = nil
logger.error("Failed to connect to \(self.destination): \(String(describing: error))")
mainQueue.addOperation { self.connectionFailed() }
return
}
while (true) {
guard let byteArray = client.read(2) else {
break
}
if (byteArray.count != 2) {
break
}
let data = Data(byteArray)
do {
let unpacked = try unpack("<h", data)
if (unpacked[0] as? Int == GSConnection.LISTEN_SEND_MORE) {
mainQueue.addOperation {
self.canSend = true
self.trySend()
}
} else {
logger.error("Unexpected message on socket from \(self.destination)")
errorOccurred(title: "Protocol Error", message: "Unexpected message from the GS")
break
}
}
catch {
logger.error("Unable to unpack message on socket from \(self.destination)")
errorOccurred(title: "Protocol Error", message: "Unexpected message from the GS")
break
}
}
client.close()
self.client = nil
mainQueue.addOperation { self.disconnect() }
}
func connect(destination : String) {
self.destination = destination
changeState(newState: .connecting)
readQueue.addOperation {
self.doConnect()
readQueue.addOperation { [weak self, destination] in
self?.logger.debug("Attempting to connect to \(destination)")
let client = TCPClient(address: destination, port: Int32(GSConnection.port))
switch client.connect(timeout: 10) {
case .success:
self?.mainQueue.addOperation {
self?.client = client
self?.connectionSuccessful()
}
case .failure(let error):
client.close()
self?.logger.error("Failed to connect to \(destination): \(String(describing: error))")
self?.mainQueue.addOperation {
self?.connectionFailed()
}
return
}
while (true) {
guard let byteArray = client.read(2) else {
break
}
if (byteArray.count != 2) {
break
}
guard let self = self else {
break
}
let data = Data(byteArray)
do {
let unpacked = try unpack("<h", data)
if (unpacked[0] as? Int == GSConnection.LISTEN_SEND_MORE) {
self.mainQueue.addOperation {
self.canSend = true
self.trySend()
}
} else {
self.logger.error("Unexpected message on socket from \(destination)")
self.errorOccurred(title: "Protocol Error", message: "Unexpected message from the GS")
break
}
}
catch {
self.logger.error("Unable to unpack message on socket from \(destination)")
self.errorOccurred(title: "Protocol Error", message: "Unexpected message from the GS")
break
}
}
self?.mainQueue.addOperation { self?.disconnect() }
}
}

View File

@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>619</string>
<string>645</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSRequiresIPhoneOS</key>

View File

@ -53,6 +53,7 @@ class SpeechForwarder : SpeechForwarderProtocol {
audioEngine.stop()
audioEngine.inputNode.removeTap(onBus: 0)
recognitionTask?.cancel()
recognitionTask?.finish()
recognitionRequest = nil
recognitionTask = nil
@ -84,12 +85,15 @@ class SpeechForwarder : SpeechForwarderProtocol {
// Create a recognition task for the speech recognition session.
// Keep a reference to the task so that it can be canceled.
recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { [weak connection] result, error in
var isFinal = false
if let result = result {
// Update the text view with the results.
OperationQueue.main.addOperation { connection.set(text: result.bestTranscription.formattedString) }
OperationQueue.main.addOperation {
guard let connection = connection else { return }
connection.set(text: result.bestTranscription.formattedString)
}
isFinal = result.isFinal
}
@ -99,6 +103,7 @@ class SpeechForwarder : SpeechForwarderProtocol {
if error != nil || isFinal {
OperationQueue.main.addOperation {
guard let connection = connection else { return }
connection.stopListening()
}
}

View File

@ -116,7 +116,7 @@ open class UDPClient: Socket {
guard let fd = self.fd else {
return (nil, "no ip", 0)
}
var buff: [Byte] = [Byte](repeating: 0x0, count: expectlen)
let buff: [Byte] = [Byte](repeating: 0x0, count: expectlen)
var remoteipbuff: [Int8] = [Int8](repeating: 0x0, count: 16)
var remoteport: Int32 = 0
let readLen: Int32 = c_yudpsocket_recive(fd, buff: buff, len: Int32(expectlen), ip: &remoteipbuff, port: &remoteport)
@ -154,7 +154,7 @@ open class UDPServer: Socket {
//TODO add multycast and boardcast
open func recv(_ expectlen: Int) -> ([Byte]?, String, Int) {
if let fd = self.fd {
var buff: [Byte] = [Byte](repeating: 0x0,count: expectlen)
let buff: [Byte] = [Byte](repeating: 0x0,count: expectlen)
var remoteipbuff: [Int8] = [Int8](repeating: 0x0,count: 16)
var remoteport: Int32 = 0
let readLen: Int32 = c_yudpsocket_recive(fd, buff: buff, len: Int32(expectlen), ip: &remoteipbuff, port: &remoteport)

View File

@ -90,7 +90,7 @@ int yudpsocket_close(int socket_fd) {
}
//return socket fd
int yudpsocket_client() {
int yudpsocket_client(void) {
//create socket
int socketfd = socket(AF_INET, SOCK_DGRAM, 0);
int reuseon = 1;

View File

@ -337,8 +337,73 @@ class ListenerGSTests: XCTestCase {
XCTAssertEqual(connection.textHeard, "Hello, everyone!")
}
// Other tests:
// - Connection deconstruction - When I remove the reference to the connection, it stays up. I think self has references because of closures that are still running in the queues.
func testDestructWhileConnected() throws {
let server = GSServerMock()
if (true) {
let connection = GSConnection()
connection.setMainQueueForTest()
XCTAssertEqual(connection.state, .disconnected)
XCTAssertNil(connection.errorMessage)
XCTAssertEqual(connection.textHeard, "")
connection.connect(destination: "127.0.0.1")
XCTAssertEqual(connection.state, .connecting)
XCTAssert(server.accept())
XCTAssert(server.hasClient())
connection.waitForMain()
waitForConnection(connection: connection)
XCTAssertEqual(connection.state, .connected)
XCTAssertNil(connection.errorMessage)
XCTAssertEqual(connection.textHeard, "")
}
XCTAssert(server.getDisconnect())
}
/* This test hangs at the getDisconnect() line at the end. Something is holding a connection reference.
func testDestructWhileListening() throws {
let server = GSServerMock()
if (true) {
let connection = GSConnection()
connection.setMainQueueForTest()
XCTAssertEqual(connection.state, .disconnected)
XCTAssertNil(connection.errorMessage)
XCTAssertEqual(connection.textHeard, "")
connection.connect(destination: "127.0.0.1")
XCTAssertEqual(connection.state, .connecting)
XCTAssert(server.accept())
XCTAssert(server.hasClient())
connection.waitForMain()
waitForConnection(connection: connection)
XCTAssertEqual(connection.state, .connected)
XCTAssertNil(connection.errorMessage)
XCTAssertEqual(connection.textHeard, "")
let speechForwarder = SpeechForwarderMock()
XCTAssert(!speechForwarder.isListening)
connection.listen(speechForwarder: speechForwarder)
XCTAssert(server.getListenState(isListening: true))
connection.waitForMain()
XCTAssert(speechForwarder.isListening)
XCTAssertEqual(connection.state, .listening)
XCTAssertNil(connection.errorMessage)
XCTAssertEqual(connection.textHeard, "")
}
XCTAssert(server.getDisconnect())
}
*/
/*
func testPerformanceExample() throws {