Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release all waiting processes when destroying a socket (fixes #15975) #16073

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 85 additions & 43 deletions src/Network-Kernel/Socket.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -465,11 +465,18 @@ Socket >> connectTo: hostAddress port: port [
Socket >> connectTo: hostAddress port: port waitForConnectionFor: timeout [
"Initiate a connection to the given port at the given host
address. Waits until the connection is established or time outs."

self connectNonBlockingTo: hostAddress port: port.
self
waitForConnectionFor: timeout
ifTimedOut: [ConnectionTimedOut signal: 'Cannot connect to '
, (NetNameResolver stringFromAddress: hostAddress) , ':' , port asString]
ifClosed: [
ConnectionClosed signal: 'Connection aborted to '
, (NetNameResolver stringFromAddress: hostAddress) , ':'
, port asString ]
ifTimedOut: [
ConnectionTimedOut signal: 'Cannot connect to '
, (NetNameResolver stringFromAddress: hostAddress) , ':'
, port asString ]
]

{ #category : 'connection open/close' }
Expand All @@ -489,18 +496,27 @@ Socket >> dataAvailable [

{ #category : 'initialize - destroy' }
Socket >> destroy [
"Destroy this socket. Its connection, if any, is aborted and its resources are freed. Do nothing if the socket has already been destroyed (i.e., if its socketHandle is nil)."
"Destroy this socket. Its connection, if any, is aborted and its resources are freed.
Any processes waiting on the socket are freed immediately, but it is up to them to
recognize that the socket has been destroyed.
Do nothing if the socket has already been destroyed (i.e., if its socketHandle is nil)."

socketHandle
ifNotNil: [
self isValid
ifTrue: [ self primSocketDestroy: socketHandle ].
Smalltalk unregisterExternalObject: semaphore.
Smalltalk unregisterExternalObject: readSemaphore.
Smalltalk unregisterExternalObject: writeSemaphore.
socketHandle := nil.
readSemaphore := writeSemaphore := semaphore := nil.
self unregister ]
socketHandle ifNotNil: [
| saveSemaphores |
self isValid ifTrue: [ self primSocketDestroy: socketHandle ].
socketHandle := nil.
Smalltalk unregisterExternalObject: semaphore.
Smalltalk unregisterExternalObject: readSemaphore.
Smalltalk unregisterExternalObject: writeSemaphore.
"Stash the semaphores and nil them before signaling to make sure
no caller gets a chance to wait on them again and block forever."
saveSemaphores := {
semaphore.
readSemaphore.
writeSemaphore }.
semaphore := readSemaphore := writeSemaphore := nil.
saveSemaphores do: [ :each | each signalAll ].
self unregister ]
]

{ #category : 'receiving' }
Expand Down Expand Up @@ -1334,6 +1350,7 @@ Socket >> retryIfWaitingForConnection: aBlock [
ifTrue: [
self
waitForConnectionFor: Socket standardTimeout
ifClosed: nil
ifTimedOut: nil.
aBlock value ]
ifFalse: [ e pass ] ]
Expand Down Expand Up @@ -1529,15 +1546,19 @@ Socket >> setPort: port [

{ #category : 'queries' }
Socket >> socketError [
^self primSocketError: socketHandle

^ socketHandle ifNotNil: [ self primSocketError: socketHandle ]
]

{ #category : 'queries' }
Socket >> socketErrorMessage [

^ [ OSPlatform current getErrorMessage: self socketError ]
on: Error
do: [ 'Error code: ' , self socketError printString ]
^ self socketError
ifNil: [ 'Socket destroyed, cannot retrieve error message' ]
ifNotNil: [ :err |
[ OSPlatform current getErrorMessage: err ]
on: Error
do: [ 'Error code: ' , err printString ] ]
]

{ #category : 'accessing' }
Expand Down Expand Up @@ -1569,40 +1590,57 @@ Socket >> unregister [
{ #category : 'waiting' }
Socket >> waitForAcceptFor: timeout [
"Wait and accept an incoming connection. Return nil if it fails"
^ self waitForAcceptFor: timeout ifTimedOut: [nil]

^ self waitForAcceptFor: timeout ifClosed: nil ifTimedOut: nil
]

{ #category : 'waiting' }
Socket >> waitForAcceptFor: timeout ifTimedOut: timeoutBlock [
Socket >> waitForAcceptFor: timeout ifClosed: closedBlock ifTimedOut: timeoutBlock [
"Wait and accept an incoming connection"
self waitForConnectionFor: timeout ifTimedOut: [^timeoutBlock value].
^self accept

self
waitForConnectionFor: timeout
ifClosed: [ ^ closedBlock value ]
ifTimedOut: [ ^ timeoutBlock value ].
^ self accept
]

{ #category : 'waiting' }
Socket >> waitForConnectionFor: timeout [
"Wait up until the given deadline for a connection to be established. Return true if it is established by the deadline, false if not."

^self
waitForConnectionFor: timeout
ifTimedOut: [ConnectionTimedOut signal: 'Failed to connect in ', timeout asString, ' seconds']
^ self
waitForConnectionFor: timeout
ifClosed: [
ConnectionClosed signal: (socketHandle
ifNil: [ 'Socket destroyed while connecting' ]
ifNotNil: [
'Connection aborted or failed: ' , self socketErrorMessage ]) ]
ifTimedOut: [
ConnectionTimedOut signal:
'Failed to connect in ' , timeout asString , ' seconds' ]
]

{ #category : 'waiting' }
Socket >> waitForConnectionFor: timeout ifTimedOut: timeoutBlock [
"Wait up until the given deadline for a connection to be established. Return true if it is established by the deadline, false if not."

| startTime msecsDelta msecsEllapsed status |
Socket >> waitForConnectionFor: timeout ifClosed: closedBlock ifTimedOut: timeoutBlock [
"Wait up until the given deadline for a connection to be established.
Evaluate closedBlock if the connection is closed locally,
or timeoutBlock if the deadline expires.

We should separately detect the case of a connection being refused here as well."

| startTime msecsDelta msecsElapsed status |
startTime := Time millisecondClockValue.
msecsDelta := (timeout * 1000) truncated.

[
status := self primSocketConnectionStatus: socketHandle.
[(status = WaitingForConnection) and: [(msecsEllapsed := Time millisecondsSince: startTime) < msecsDelta]]
whileTrue: [
semaphore waitTimeoutMilliseconds: msecsDelta - msecsEllapsed.
status := self primSocketConnectionStatus: socketHandle].
status == WaitingForConnection and: [
(msecsElapsed := Time millisecondsSince: startTime) < msecsDelta ] ]
whileTrue: [ semaphore waitTimeoutMilliseconds: msecsDelta - msecsElapsed ].

status = Connected ifFalse: [^timeoutBlock value].
^ true
status == WaitingForConnection ifTrue: [ ^ timeoutBlock value ].
status == Connected ifFalse: [ ^ closedBlock value ]
]

{ #category : 'waiting' }
Expand All @@ -1628,7 +1666,9 @@ Socket >> waitForDataFor: timeout [

{ #category : 'waiting' }
Socket >> waitForDataFor: timeout ifClosed: closedBlock ifTimedOut: timedOutBlock [
"Wait for the given nr of seconds for data to arrive."
"Wait for the given nr of seconds for data to arrive.
If it does not, execute <timedOutBlock>. If the connection
is closed before any data arrives, execute <closedBlock>."

| startTime msecsDelta msecsElapsed |
startTime := Time millisecondClockValue.
Expand Down Expand Up @@ -1662,22 +1702,24 @@ Socket >> waitForDisconnectionFor: timeout [
(e.g., because he has called 'close' to send a close request to the other end)
before calling this method."

| startTime msecsDelta status |
| startTime msecsDelta msecsElapsed status |
startTime := Time millisecondClockValue.
msecsDelta := (timeout * 1000) truncated.

[
status := self primSocketConnectionStatus: socketHandle.
[((status == Connected) or: [(status == ThisEndClosed)]) and:
[(Time millisecondsSince: startTime) < msecsDelta]] whileTrue: [
self discardReceivedData.
self readSemaphore waitTimeoutMilliseconds:
(msecsDelta - (Time millisecondsSince: startTime) max: 0).
status := self primSocketConnectionStatus: socketHandle].
(status == Connected or: [ status == ThisEndClosed ]) and: [
(msecsElapsed := Time millisecondsSince: startTime) < msecsDelta ] ]
whileTrue: [
self discardReceivedData.
self readSemaphore waitTimeoutMilliseconds: msecsDelta - msecsElapsed ].
^ status ~= Connected
]

{ #category : 'waiting' }
Socket >> waitForSendDoneFor: timeout [
"Wait up until the given deadline for the current send operation to complete. Return true if it completes by the deadline, false if not."
"Wait up until the given deadline for the current send operation to complete.
Raise an exception if the timeout expires or the connection is closed before sending finishes."

^ self
waitForSendDoneFor: timeout
Expand Down