io: change IOException treatment from "fatal error" to "expected during normal operation"
Up to now IOExceptions during reading, writing or connecting were treated as fatal actor errors, crashing the connection actor and thus producing ERROR level log messages. This patch treats such exceptions as "expected" during normal operation and prevents them from crashing the actor. Rather, they are logged at DEBUG level and the actor is actively and cleanly stopped.
This commit is contained in:
parent
8afd7a440d
commit
b311e9e700
3 changed files with 35 additions and 37 deletions
|
|
@ -457,12 +457,11 @@ class TcpConnectionSpec extends AkkaSpec("""
|
||||||
|
|
||||||
"report when peer aborted the connection" in new EstablishedConnectionTest() {
|
"report when peer aborted the connection" in new EstablishedConnectionTest() {
|
||||||
run {
|
run {
|
||||||
EventFilter[IOException](occurrences = 1) intercept {
|
abortClose(serverSideChannel)
|
||||||
abortClose(serverSideChannel)
|
selector.send(connectionActor, ChannelReadable)
|
||||||
selector.send(connectionActor, ChannelReadable)
|
val err = connectionHandler.expectMsgType[ErrorClosed]
|
||||||
val err = connectionHandler.expectMsgType[ErrorClosed]
|
err.cause must be(ConnectionResetByPeerMessage)
|
||||||
err.cause must be(ConnectionResetByPeerMessage)
|
|
||||||
}
|
|
||||||
// wait a while
|
// wait a while
|
||||||
connectionHandler.expectNoMsg(200.millis)
|
connectionHandler.expectNoMsg(200.millis)
|
||||||
|
|
||||||
|
|
@ -475,11 +474,9 @@ class TcpConnectionSpec extends AkkaSpec("""
|
||||||
val writer = TestProbe()
|
val writer = TestProbe()
|
||||||
|
|
||||||
abortClose(serverSideChannel)
|
abortClose(serverSideChannel)
|
||||||
EventFilter[IOException](occurrences = 1) intercept {
|
writer.send(connectionActor, Write(ByteString("testdata")))
|
||||||
writer.send(connectionActor, Write(ByteString("testdata")))
|
// bother writer and handler should get the message
|
||||||
// bother writer and handler should get the message
|
writer.expectMsgType[ErrorClosed]
|
||||||
writer.expectMsgType[ErrorClosed]
|
|
||||||
}
|
|
||||||
connectionHandler.expectMsgType[ErrorClosed]
|
connectionHandler.expectMsgType[ErrorClosed]
|
||||||
|
|
||||||
assertThisConnectionActorTerminated()
|
assertThisConnectionActorTerminated()
|
||||||
|
|
@ -501,10 +498,8 @@ class TcpConnectionSpec extends AkkaSpec("""
|
||||||
key.isConnectable must be(true)
|
key.isConnectable must be(true)
|
||||||
val forceThisLazyVal = connectionActor.toString
|
val forceThisLazyVal = connectionActor.toString
|
||||||
Thread.sleep(300)
|
Thread.sleep(300)
|
||||||
EventFilter[ConnectException](occurrences = 1) intercept {
|
selector.send(connectionActor, ChannelConnectable)
|
||||||
selector.send(connectionActor, ChannelConnectable)
|
userHandler.expectMsg(CommandFailed(Connect(UnboundAddress)))
|
||||||
userHandler.expectMsg(CommandFailed(Connect(UnboundAddress)))
|
|
||||||
}
|
|
||||||
|
|
||||||
verifyActorTermination(connectionActor)
|
verifyActorTermination(connectionActor)
|
||||||
} finally sel.close()
|
} finally sel.close()
|
||||||
|
|
@ -516,9 +511,7 @@ class TcpConnectionSpec extends AkkaSpec("""
|
||||||
override lazy val connectionActor = createConnectionActor(serverAddress = UnboundAddress, timeout = Option(100.millis))
|
override lazy val connectionActor = createConnectionActor(serverAddress = UnboundAddress, timeout = Option(100.millis))
|
||||||
run {
|
run {
|
||||||
connectionActor.toString must not be ("")
|
connectionActor.toString must not be ("")
|
||||||
EventFilter[SocketTimeoutException](occurrences = 1) intercept {
|
userHandler.expectMsg(CommandFailed(Connect(UnboundAddress, timeout = Option(100.millis))))
|
||||||
userHandler.expectMsg(CommandFailed(Connect(UnboundAddress, timeout = Option(100.millis))))
|
|
||||||
}
|
|
||||||
verifyActorTermination(connectionActor)
|
verifyActorTermination(connectionActor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -250,13 +250,12 @@ private[io] abstract class TcpConnection(val tcp: TcpExt, val channel: SocketCha
|
||||||
def doCloseConnection(handler: ActorRef, closeCommander: Option[ActorRef], closedEvent: ConnectionClosed): Unit = {
|
def doCloseConnection(handler: ActorRef, closeCommander: Option[ActorRef], closedEvent: ConnectionClosed): Unit = {
|
||||||
if (closedEvent == Aborted) abort()
|
if (closedEvent == Aborted) abort()
|
||||||
else channel.close()
|
else channel.close()
|
||||||
closedMessage = CloseInformation(Set(handler) ++ closeCommander, closedEvent)
|
stopWith(CloseInformation(Set(handler) ++ closeCommander, closedEvent))
|
||||||
context.stop(self)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def handleError(handler: ActorRef, exception: IOException): Nothing = {
|
def handleError(handler: ActorRef, exception: IOException): Unit = {
|
||||||
closedMessage = CloseInformation(Set(handler), ErrorClosed(extractMsg(exception)))
|
log.debug("Closing connection due to IO error {}", exception)
|
||||||
throw exception
|
stopWith(CloseInformation(Set(handler), ErrorClosed(extractMsg(exception))))
|
||||||
}
|
}
|
||||||
|
|
||||||
@tailrec private[this] def extractMsg(t: Throwable): String =
|
@tailrec private[this] def extractMsg(t: Throwable): String =
|
||||||
|
|
@ -279,6 +278,11 @@ private[io] abstract class TcpConnection(val tcp: TcpExt, val channel: SocketCha
|
||||||
channel.close()
|
channel.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def stopWith(closeInfo: CloseInformation): Unit = {
|
||||||
|
closedMessage = closeInfo
|
||||||
|
context.stop(self)
|
||||||
|
}
|
||||||
|
|
||||||
override def postStop(): Unit = {
|
override def postStop(): Unit = {
|
||||||
if (channel.isOpen)
|
if (channel.isOpen)
|
||||||
abort()
|
abort()
|
||||||
|
|
@ -349,7 +353,7 @@ private[io] abstract class TcpConnection(val tcp: TcpExt, val channel: SocketCha
|
||||||
}
|
}
|
||||||
|
|
||||||
try innerWrite(this)
|
try innerWrite(this)
|
||||||
catch { case e: IOException ⇒ handleError(info.handler, e) }
|
catch { case e: IOException ⇒ handleError(info.handler, e); this }
|
||||||
}
|
}
|
||||||
def hasData = buffer.hasRemaining || remainingData.nonEmpty
|
def hasData = buffer.hasRemaining || remainingData.nonEmpty
|
||||||
def consume(writtenBytes: Int): PendingBufferWrite =
|
def consume(writtenBytes: Int): PendingBufferWrite =
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,16 @@
|
||||||
|
|
||||||
package akka.io
|
package akka.io
|
||||||
|
|
||||||
import akka.actor.{ ReceiveTimeout, ActorRef }
|
|
||||||
import akka.io.Inet.SocketOption
|
|
||||||
import akka.io.SelectionHandler._
|
|
||||||
import akka.io.Tcp._
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.channels.{ SelectionKey, SocketChannel }
|
import java.nio.channels.{ SelectionKey, SocketChannel }
|
||||||
|
import java.net.ConnectException
|
||||||
import scala.collection.immutable
|
import scala.collection.immutable
|
||||||
import scala.concurrent.duration.Duration
|
import scala.concurrent.duration.Duration
|
||||||
import java.net.{ ConnectException, SocketTimeoutException }
|
import akka.actor.{ ReceiveTimeout, ActorRef }
|
||||||
|
import akka.io.Inet.SocketOption
|
||||||
|
import akka.io.TcpConnection.CloseInformation
|
||||||
|
import akka.io.SelectionHandler._
|
||||||
|
import akka.io.Tcp._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An actor handling the connection state machine for an outgoing connection
|
* An actor handling the connection state machine for an outgoing connection
|
||||||
|
|
@ -45,7 +46,9 @@ private[io] class TcpOutgoingConnection(_tcp: TcpExt,
|
||||||
}
|
}
|
||||||
|
|
||||||
def connecting(registration: ChannelRegistration, commander: ActorRef,
|
def connecting(registration: ChannelRegistration, commander: ActorRef,
|
||||||
options: immutable.Traversable[SocketOption]): Receive =
|
options: immutable.Traversable[SocketOption]): Receive = {
|
||||||
|
def stop(): Unit = stopWith(CloseInformation(Set(commander), connect.failureMessage))
|
||||||
|
|
||||||
{
|
{
|
||||||
case ChannelConnectable ⇒
|
case ChannelConnectable ⇒
|
||||||
if (timeout.isDefined) context.setReceiveTimeout(Duration.Undefined) // Clear the timeout
|
if (timeout.isDefined) context.setReceiveTimeout(Duration.Undefined) // Clear the timeout
|
||||||
|
|
@ -55,16 +58,14 @@ private[io] class TcpOutgoingConnection(_tcp: TcpExt,
|
||||||
completeConnect(registration, commander, options)
|
completeConnect(registration, commander, options)
|
||||||
} catch {
|
} catch {
|
||||||
case e: IOException ⇒
|
case e: IOException ⇒
|
||||||
if (tcp.Settings.TraceLogging) log.debug("Could not establish connection due to {}", e)
|
log.debug("Could not establish connection to [{}] due to {}", remoteAddress, e)
|
||||||
closedMessage = TcpConnection.CloseInformation(Set(commander), connect.failureMessage)
|
stop()
|
||||||
throw e
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case ReceiveTimeout ⇒
|
case ReceiveTimeout ⇒
|
||||||
if (timeout.isDefined) context.setReceiveTimeout(Duration.Undefined) // Clear the timeout
|
if (timeout.isDefined) context.setReceiveTimeout(Duration.Undefined) // Clear the timeout
|
||||||
val failure = new SocketTimeoutException(s"Connection to [$remoteAddress] timed out")
|
log.debug("Connect timeout expired, could not establish connection to [{}]", remoteAddress)
|
||||||
if (tcp.Settings.TraceLogging) log.debug("Could not establish connection due to {}", failure)
|
stop()
|
||||||
closedMessage = TcpConnection.CloseInformation(Set(commander), connect.failureMessage)
|
|
||||||
throw failure
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue