@@ -465,11 +465,18 @@ Socket >> connectTo: hostAddress port: port [
465465Socket >> connectTo: hostAddress port: port waitForConnectionFor: timeout [
466466 " Initiate a connection to the given port at the given host
467467 address. Waits until the connection is established or time outs."
468+
468469 self connectNonBlockingTo: hostAddress port: port.
469470 self
470471 waitForConnectionFor: timeout
471- ifTimedOut: [ConnectionTimedOut signal : ' Cannot connect to '
472- , (NetNameResolver stringFromAddress: hostAddress) , ' :' , port asString]
472+ ifClosed: [
473+ ConnectionClosed signal : ' Connection aborted to '
474+ , (NetNameResolver stringFromAddress: hostAddress) , ' :'
475+ , port asString ]
476+ ifTimedOut: [
477+ ConnectionTimedOut signal : ' Cannot connect to '
478+ , (NetNameResolver stringFromAddress: hostAddress) , ' :'
479+ , port asString ]
473480]
474481
475482{ #category : ' connection open/close' }
@@ -489,18 +496,30 @@ Socket >> dataAvailable [
489496
490497{ #category : ' initialize - destroy' }
491498Socket >> destroy [
492- " 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)."
499+ " Destroy this socket. Its connection, if any, is aborted and its resources are freed.
500+ Any processes waiting on the socket are freed immediately, but it is up to them to
501+ recognize that the socket has been destroyed.
502+ Do nothing if the socket has already been destroyed (i.e., if its socketHandle is nil)."
493503
494- socketHandle
495- ifNotNil: [
496- self isValid
497- ifTrue: [ self primSocketDestroy: socketHandle ].
498- Smalltalk unregisterExternalObject: semaphore.
499- Smalltalk unregisterExternalObject: readSemaphore.
500- Smalltalk unregisterExternalObject: writeSemaphore.
501- socketHandle := nil .
502- readSemaphore := writeSemaphore := semaphore := nil .
503- self unregister ]
504+ socketHandle ifNotNil: [
505+ | saveSemaphores |
506+ self isValid ifTrue: [ self primSocketDestroy: socketHandle ].
507+ socketHandle := nil .
508+ Smalltalk unregisterExternalObject: semaphore.
509+ Smalltalk unregisterExternalObject: readSemaphore.
510+ Smalltalk unregisterExternalObject: writeSemaphore.
511+ " Stash the semaphores and nil them before signaling to make sure
512+ no caller gets a chance to wait on them again and block forever."
513+ saveSemaphores := {
514+ semaphore.
515+ readSemaphore.
516+ writeSemaphore }.
517+ semaphore := readSemaphore := writeSemaphore := nil .
518+ " A single #signal should be sufficient, as multiple processes trying to
519+ read or write at once will result in undefined behavior anyway as their
520+ data gets all mixed up together."
521+ saveSemaphores do: [ :each | each signal ].
522+ self unregister ]
504523]
505524
506525{ #category : ' receiving' }
@@ -1334,6 +1353,7 @@ Socket >> retryIfWaitingForConnection: aBlock [
13341353 ifTrue: [
13351354 self
13361355 waitForConnectionFor: Socket standardTimeout
1356+ ifClosed: nil
13371357 ifTimedOut: nil .
13381358 aBlock value ]
13391359 ifFalse: [ e pass ] ]
@@ -1529,15 +1549,19 @@ Socket >> setPort: port [
15291549
15301550{ #category : ' queries' }
15311551Socket >> socketError [
1532- ^ self primSocketError: socketHandle
1552+
1553+ ^ socketHandle ifNotNil: [ self primSocketError: socketHandle ]
15331554]
15341555
15351556{ #category : ' queries' }
15361557Socket >> socketErrorMessage [
15371558
1538- ^ [ OSPlatform current getErrorMessage: self socketError ]
1539- on: Error
1540- do: [ ' Error code: ' , self socketError printString ]
1559+ ^ self socketError
1560+ ifNil: [ ' Socket destroyed, cannot retrieve error message' ]
1561+ ifNotNil: [ :err |
1562+ [ OSPlatform current getErrorMessage: err ]
1563+ on: Error
1564+ do: [ ' Error code: ' , err printString ] ]
15411565]
15421566
15431567{ #category : ' accessing' }
@@ -1569,40 +1593,57 @@ Socket >> unregister [
15691593{ #category : ' waiting' }
15701594Socket >> waitForAcceptFor: timeout [
15711595 " Wait and accept an incoming connection. Return nil if it fails"
1572- ^ self waitForAcceptFor: timeout ifTimedOut: [nil ]
1596+
1597+ ^ self waitForAcceptFor: timeout ifClosed: nil ifTimedOut: nil
15731598]
15741599
15751600{ #category : ' waiting' }
1576- Socket >> waitForAcceptFor: timeout ifTimedOut: timeoutBlock [
1601+ Socket >> waitForAcceptFor: timeout ifClosed: closedBlock ifTimedOut: timeoutBlock [
15771602 " Wait and accept an incoming connection"
1578- self waitForConnectionFor: timeout ifTimedOut: [^ timeoutBlock value].
1579- ^ self accept
1603+
1604+ self
1605+ waitForConnectionFor: timeout
1606+ ifClosed: [ ^ closedBlock value ]
1607+ ifTimedOut: [ ^ timeoutBlock value ].
1608+ ^ self accept
15801609]
15811610
15821611{ #category : ' waiting' }
15831612Socket >> waitForConnectionFor: timeout [
15841613 " Wait up until the given deadline for a connection to be established. Return true if it is established by the deadline, false if not."
15851614
1586- ^ self
1587- waitForConnectionFor: timeout
1588- ifTimedOut: [ConnectionTimedOut signal : ' Failed to connect in ' , timeout asString, ' seconds' ]
1615+ ^ self
1616+ waitForConnectionFor: timeout
1617+ ifClosed: [
1618+ ConnectionClosed signal : (socketHandle
1619+ ifNil: [ ' Socket destroyed while connecting' ]
1620+ ifNotNil: [
1621+ ' Connection aborted or failed: ' , self socketErrorMessage ]) ]
1622+ ifTimedOut: [
1623+ ConnectionTimedOut signal :
1624+ ' Failed to connect in ' , timeout asString , ' seconds' ]
15891625]
15901626
15911627{ #category : ' waiting' }
1592- Socket >> waitForConnectionFor: timeout ifTimedOut: timeoutBlock [
1593- " Wait up until the given deadline for a connection to be established. Return true if it is established by the deadline, false if not."
1628+ Socket >> waitForConnectionFor: timeout ifClosed: closedBlock ifTimedOut: timeoutBlock [
1629+ " Wait up until the given deadline for a connection to be established.
1630+ Evaluate closedBlock if the connection is closed locally,
1631+ or timeoutBlock if the deadline expires.
1632+
1633+ We should separately detect the case of a connection being refused here as well."
15941634
1595- | startTime msecsDelta msecsEllapsed status |
1635+ | startTime msecsDelta msecsElapsed status |
15961636 startTime := Time millisecondClockValue.
15971637 msecsDelta := (timeout * 1000 ) truncated.
1638+
1639+ [
15981640 status := self primSocketConnectionStatus: socketHandle.
1599- [(status = WaitingForConnection ) and : [(msecsEllapsed := Time millisecondsSince: startTime) < msecsDelta]]
1600- whileTrue: [
1601- semaphore waitTimeoutMilliseconds: msecsDelta - msecsEllapsed.
1602- status := self primSocketConnectionStatus: socketHandle].
1641+ status == WaitingForConnection and : [
1642+ (msecsElapsed := Time millisecondsSince: startTime) < msecsDelta ] ]
1643+ whileTrue: [ semaphore waitTimeoutMilliseconds: msecsDelta - msecsElapsed ].
16031644
1604- status = Connected ifFalse : [^ timeoutBlock value].
1605- ^ true
1645+ status == WaitingForConnection ifTrue : [ ^ timeoutBlock value ].
1646+ status == Connected ifFalse: [ ^ closedBlock value ]
16061647]
16071648
16081649{ #category : ' waiting' }
@@ -1628,7 +1669,9 @@ Socket >> waitForDataFor: timeout [
16281669
16291670{ #category : ' waiting' }
16301671Socket >> waitForDataFor: timeout ifClosed: closedBlock ifTimedOut: timedOutBlock [
1631- " Wait for the given nr of seconds for data to arrive."
1672+ " Wait for the given nr of seconds for data to arrive.
1673+ If it does not, execute <timedOutBlock>. If the connection
1674+ is closed before any data arrives, execute <closedBlock>."
16321675
16331676 | startTime msecsDelta msecsElapsed |
16341677 startTime := Time millisecondClockValue.
@@ -1637,21 +1680,17 @@ Socket >> waitForDataFor: timeout ifClosed: closedBlock ifTimedOut: timedOutBloc
16371680 self isConnected ifFalse: [ ^ closedBlock value ].
16381681 (msecsElapsed := Time millisecondsSince: startTime) < msecsDelta
16391682 ifFalse: [ ^ timedOutBlock value ].
1640- self readSemaphore waitTimeoutMilliseconds: msecsDelta - msecsElapsed ]
1683+ readSemaphore waitTimeoutMilliseconds: msecsDelta - msecsElapsed ]
16411684]
16421685
16431686{ #category : ' waiting' }
16441687Socket >> waitForDataIfClosed: closedBlock [
16451688 " Wait indefinitely for data to arrive. This method will block until
16461689 data is available or the socket is closed."
16471690
1648- [true ]
1649- whileTrue: [
1650- self dataAvailable
1651- ifTrue: [^ self ].
1652- self isConnected
1653- ifFalse: [^ closedBlock value].
1654- self readSemaphore wait]
1691+ [ self dataAvailable ] whileFalse: [
1692+ self isConnected ifFalse: [ ^ closedBlock value ].
1693+ readSemaphore wait ]
16551694]
16561695
16571696{ #category : ' waiting' }
@@ -1662,22 +1701,23 @@ Socket >> waitForDisconnectionFor: timeout [
16621701 (e.g., because he has called 'close' to send a close request to the other end)
16631702 before calling this method."
16641703
1665- | startTime msecsDelta status |
1704+ | startTime msecsDelta msecsElapsed status |
16661705 startTime := Time millisecondClockValue.
16671706 msecsDelta := (timeout * 1000 ) truncated.
1707+
1708+ [
16681709 status := self primSocketConnectionStatus: socketHandle.
1669- [((status == Connected ) or : [(status == ThisEndClosed )]) and :
1670- [(Time millisecondsSince: startTime) < msecsDelta]] whileTrue: [
1671- self discardReceivedData.
1672- self readSemaphore waitTimeoutMilliseconds:
1673- (msecsDelta - (Time millisecondsSince: startTime) max: 0 ).
1674- status := self primSocketConnectionStatus: socketHandle].
1710+ (status == Connected or : [ status == ThisEndClosed ]) and : [
1711+ (msecsElapsed := Time millisecondsSince: startTime) < msecsDelta ] ]
1712+ whileTrue: [
1713+ self discardReceivedData.
1714+ readSemaphore waitTimeoutMilliseconds: msecsDelta - msecsElapsed ].
16751715 ^ status ~= Connected
16761716]
16771717
16781718{ #category : ' waiting' }
16791719Socket >> waitForSendDoneFor: timeout [
1680- " Wait up until the given deadline for the current send operation to complete.
1720+ " Wait up until the given deadline for the current send operation to complete.
16811721 Raise an exception if the timeout expires or the connection is closed before sending finishes."
16821722
16831723 ^ self
@@ -1691,18 +1731,21 @@ Socket >> waitForSendDoneFor: timeout ifClosed: closedBlock ifTimedOut: timedOut
16911731 " Wait up until the given deadline for the current send operation to complete.
16921732 If it does not, execute <timedOutBlock>. If the connection is closed before
16931733 the send completes, execute <closedBlock>."
1694- | startTime msecsDelta msecsElapsed sendDone |
1695-
1734+
1735+ | startTime msecsDelta msecsElapsed |
16961736 startTime := Time millisecondClockValue.
16971737 msecsDelta := (timeout * 1000 ) truncated.
16981738 " Connection end and final data can happen fast, so test in this order"
1699- [ sendDone := self sendDone ] whileFalse: [
1739+ [ self sendDone ] whileFalse: [
17001740 self isConnected ifFalse: [ ^ closedBlock value ].
17011741 (msecsElapsed := Time millisecondsSince: startTime) < msecsDelta
17021742 ifFalse: [ ^ timedOutBlock value ].
1703- self writeSemaphore waitTimeoutMilliseconds: msecsDelta - msecsElapsed ].
1704-
1705- ^ sendDone
1743+ writeSemaphore waitTimeoutMilliseconds: msecsDelta - msecsElapsed ].
1744+
1745+ " For backward compatibility with Pharo <= 11, return a boolean indicating
1746+ whether the send is completed. The loop will only terminate when this
1747+ is the case, so simply return true."
1748+ ^ true
17061749]
17071750
17081751{ #category : ' accessing' }
0 commit comments