[CORDA-1879]: Ensure Node dies on unrecoverable errors. (#4213)

This commit is contained in:
Michele Sollecito 2018-11-12 15:56:04 +00:00 committed by GitHub
parent ac23fcdf24
commit dc62b20c5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 83 additions and 282 deletions

View File

@ -204,13 +204,13 @@ class NodeMonitorModel : AutoCloseable {
val nodeInfo = _connection.proxy.nodeInfo()
require(nodeInfo.legalIdentitiesAndCerts.isNotEmpty())
_connection
} catch (throwable: Throwable) {
} catch (exception: Exception) {
if (shouldRetry) {
// Deliberately not logging full stack trace as it will be full of internal stacktraces.
logger.info("Exception upon establishing connection: {}", throwable.message)
logger.info("Exception upon establishing connection: {}", exception.message)
null
} else {
throw throwable
throw exception
}
}

View File

@ -116,7 +116,7 @@ class CordaRPCClientTest : NodeBasedTest(listOf("net.corda.finance")) {
nodeIsShut.onCompleted()
} catch (e: ActiveMQSecurityException) {
// nothing here - this happens if trying to connect before the node is started
} catch (e: Throwable) {
} catch (e: Exception) {
nodeIsShut.onError(e)
}
}, 1, 1, TimeUnit.SECONDS)

View File

@ -564,8 +564,8 @@ class RPCClientProxyHandler(
observationExecutorPool.run(k) {
try {
m[k]?.onError(ConnectionFailureException())
} catch (th: Throwable) {
log.error("Unexpected exception when RPC connection failure handling", th)
} catch (e: Exception) {
log.error("Unexpected exception when RPC connection failure handling", e)
}
}
}

View File

@ -4,6 +4,7 @@ import net.corda.core.KeepForDJVM
import net.corda.core.internal.extractFile
import net.corda.core.serialization.CordaSerializable
import java.io.FileNotFoundException
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.security.PublicKey
@ -36,10 +37,10 @@ interface Attachment : NamedByHash {
@JvmDefault
fun openAsJAR(): JarInputStream {
val stream = open()
try {
return JarInputStream(stream)
} catch (t: Throwable) {
stream.use { throw t }
return try {
JarInputStream(stream)
} catch (e: IOException) {
stream.use { throw e }
}
}

View File

@ -71,8 +71,8 @@ fun <V, W> CordaFuture<out V>.flatMap(transform: (V) -> CordaFuture<out W>): Cor
thenMatch(success@ {
result.captureLater(try {
transform(it)
} catch (t: Throwable) {
result.setException(t)
} catch (e: Exception) {
result.setException(e)
return@success
})
}, {
@ -128,8 +128,8 @@ interface ValueOrException<in V> {
fun capture(block: () -> V): Boolean {
return set(try {
block()
} catch (t: Throwable) {
return setException(t)
} catch (e: Exception) {
return setException(e)
})
}
}
@ -153,8 +153,8 @@ internal class CordaFutureImpl<V>(private val impl: CompletableFuture<V> = Compl
impl.whenComplete { _, _ ->
try {
callback(this)
} catch (t: Throwable) {
log.error(listenerFailedMessage, t)
} catch (e: Exception) {
log.error(listenerFailedMessage, e)
}
}
}

View File

@ -193,7 +193,7 @@ data class LedgerTransaction @JvmOverloads constructor(
is Try.Success -> {
try {
contractInstances.add(result.value.newInstance())
} catch (e: Throwable) {
} catch (e: Exception) {
throw TransactionVerificationException.ContractCreationError(id, result.value.name, e)
}
}
@ -202,7 +202,7 @@ data class LedgerTransaction @JvmOverloads constructor(
contractInstances.forEach { contract ->
try {
contract.verify(this)
} catch (e: Throwable) {
} catch (e: Exception) {
throw TransactionVerificationException.ContractRejection(id, contract, e)
}
}

View File

@ -19,8 +19,8 @@ sealed class Try<out A> {
inline fun <T> on(body: () -> T): Try<T> {
return try {
Success(body())
} catch (t: Throwable) {
Failure(t)
} catch (e: Exception) {
Failure(e)
}
}
}

View File

@ -1,31 +0,0 @@
ext {
javaassist_version = "3.12.1.GA"
}
apply plugin: 'kotlin'
apply plugin: 'idea'
description 'A javaagent to allow hooking into Kryo'
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "javassist:javassist:$javaassist_version"
compile "com.esotericsoftware:kryo:4.0.0"
compile "$quasar_group:quasar-core:$quasar_version:jdk8"
}
jar {
archiveName = "${project.name}.jar"
manifest {
attributes(
'Premain-Class': 'net.corda.kryohook.KryoHookAgent',
'Can-Redefine-Classes': 'true',
'Can-Retransform-Classes': 'true',
'Can-Set-Native-Method-Prefix': 'true',
'Implementation-Title': "KryoHook",
'Implementation-Version': rootProject.version
)
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}

View File

@ -1,164 +0,0 @@
package net.corda.kryohook
import co.paralleluniverse.strands.Strand
import com.esotericsoftware.kryo.Kryo
import com.esotericsoftware.kryo.io.Output
import javassist.ClassPool
import javassist.CtClass
import java.lang.instrument.ClassFileTransformer
import java.lang.instrument.Instrumentation
import java.security.ProtectionDomain
import java.util.concurrent.ConcurrentHashMap
class KryoHookAgent {
companion object {
@JvmStatic
fun premain(@Suppress("UNUSED_PARAMETER") argumentsString: String?, instrumentation: Instrumentation) {
Runtime.getRuntime().addShutdownHook(Thread {
val statsTrees = KryoHook.events.values.flatMap {
readTrees(it, 0).second
}
val builder = StringBuilder()
statsTrees.forEach {
prettyStatsTree(0, it, builder)
}
print(builder.toString())
})
instrumentation.addTransformer(KryoHook)
}
}
}
fun prettyStatsTree(indent: Int, statsTree: StatsTree, builder: StringBuilder) {
when (statsTree) {
is StatsTree.Object -> {
builder.append(kotlin.CharArray(indent) { ' ' })
builder.append(statsTree.className)
builder.append(" ")
builder.append(statsTree.size)
builder.append("\n")
for (child in statsTree.children) {
prettyStatsTree(indent + 2, child, builder)
}
}
}
}
/**
* The hook simply records the write() entries and exits together with the output offset at the time of the call.
* This is recorded in a StrandID -> List<StatsEvent> map.
*
* Later we "parse" these lists into a tree.
*/
object KryoHook : ClassFileTransformer {
val classPool = ClassPool.getDefault()!!
val hookClassName = javaClass.name!!
override fun transform(
loader: ClassLoader?,
className: String,
classBeingRedefined: Class<*>?,
protectionDomain: ProtectionDomain?,
classfileBuffer: ByteArray
): ByteArray? {
if (className.startsWith("java") || className.startsWith("javassist") || className.startsWith("kotlin")) {
return null
}
return try {
val clazz = classPool.makeClass(classfileBuffer.inputStream())
instrumentClass(clazz)?.toBytecode()
} catch (throwable: Throwable) {
println("SOMETHING WENT WRONG")
throwable.printStackTrace(System.out)
null
}
}
private fun instrumentClass(clazz: CtClass): CtClass? {
for (method in clazz.declaredBehaviors) {
if (method.name == "write") {
val parameterTypeNames = method.parameterTypes.map { it.name }
if (parameterTypeNames == listOf("com.esotericsoftware.kryo.Kryo", "com.esotericsoftware.kryo.io.Output", "java.lang.Object")) {
if (method.isEmpty) continue
println("Instrumenting ${clazz.name}")
method.insertBefore("$hookClassName.${this::writeEnter.name}($1, $2, $3);")
method.insertAfter("$hookClassName.${this::writeExit.name}($1, $2, $3);")
return clazz
}
}
}
return null
}
// StrandID -> StatsEvent map
val events = ConcurrentHashMap<Long, ArrayList<StatsEvent>>()
@JvmStatic
fun writeEnter(@Suppress("UNUSED_PARAMETER") kryo: Kryo, output: Output, obj: Any) {
events.getOrPut(Strand.currentStrand().id) { ArrayList() }.add(
StatsEvent.Enter(obj.javaClass.name, output.total())
)
}
@JvmStatic
fun writeExit(@Suppress("UNUSED_PARAMETER") kryo: Kryo, output: Output, obj: Any) {
events[Strand.currentStrand().id]!!.add(
StatsEvent.Exit(obj.javaClass.name, output.total())
)
}
}
/**
* TODO we could add events on entries/exits to field serializers to get more info on what's being serialised.
*/
sealed class StatsEvent {
data class Enter(val className: String, val offset: Long) : StatsEvent()
data class Exit(val className: String, val offset: Long) : StatsEvent()
}
/**
* TODO add Field constructor.
*/
sealed class StatsTree {
data class Object(
val className: String,
val size: Long,
val children: List<StatsTree>
) : StatsTree()
}
fun readTree(events: List<StatsEvent>, index: Int): Pair<Int, StatsTree> {
val event = events[index]
when (event) {
is StatsEvent.Enter -> {
val (nextIndex, children) = readTrees(events, index + 1)
val exit = events[nextIndex] as StatsEvent.Exit
require(event.className == exit.className)
return Pair(nextIndex + 1, StatsTree.Object(event.className, exit.offset - event.offset, children))
}
is StatsEvent.Exit -> {
throw IllegalStateException("Wasn't expecting Exit")
}
}
}
fun readTrees(events: List<StatsEvent>, index: Int): Pair<Int, List<StatsTree>> {
val trees = ArrayList<StatsTree>()
var i = index
while (true) {
val event = events.getOrNull(i)
when (event) {
is StatsEvent.Enter -> {
val (nextIndex, tree) = readTree(events, i)
trees.add(tree)
i = nextIndex
}
is StatsEvent.Exit -> {
return Pair(i, trees)
}
null -> {
return Pair(i, trees)
}
}
}
}

View File

@ -1,23 +0,0 @@
What is this
------------
This is a javaagent that hooks into Kryo serializers to record a breakdown of how many bytes objects take in the output.
The dump is quite ugly now, but the in-memory representation is a simple tree so we could put some nice visualisation on
top if we want.
How do I run it
---------------
Build the agent:
```
./gradlew experimental:kryo-hook:jar
```
Add this JVM flag to what you're running:
```
-javaagent:<PROJECT>/experimental/kryo-hook/build/libs/kryo-hook.jar
```
The agent will dump the output when the JVM shuts down.

View File

@ -191,6 +191,7 @@ object QuasarInstrumentationHook : ClassFileTransformer {
} catch (throwable: Throwable) {
println("SOMETHING WENT WRONG")
throwable.printStackTrace(System.out)
if (throwable is VirtualMachineError) throw throwable
classfileBuffer
}
}

View File

@ -220,8 +220,8 @@ object RPCApi {
companion object {
private fun Any.safeSerialize(context: SerializationContext, wrap: (Throwable) -> Any) = try {
serialize(context = context)
} catch (t: Throwable) {
wrap(t).serialize(context = context)
} catch (e: Exception) {
wrap(e).serialize(context = context)
}
fun fromClientMessage(context: SerializationContext, message: ClientMessage): ServerToClient {

View File

@ -160,8 +160,8 @@ class CordaPersistence(
var recoverableFailureCount = 0
fun <T> quietly(task: () -> T) = try {
task()
} catch (t: Throwable) {
log.warn("Cleanup task failed:", t)
} catch (e: Exception) {
log.warn("Cleanup task failed:", e)
}
while (true) {
val transaction = contextDatabase.currentOrNew(isolationLevel) // XXX: Does this code really support statement changing the contextDatabase?
@ -169,7 +169,7 @@ class CordaPersistence(
val answer = transaction.statement()
transaction.commit()
return answer
} catch (e: Throwable) {
} catch (e: Exception) {
quietly(transaction::rollback)
if (e is SQLException || (recoverAnyNestedSQLException && e.hasSQLExceptionCause())) {
if (++recoverableFailureCount > recoverableFailureTolerance) throw e

View File

@ -318,7 +318,7 @@ class X509UtilitiesTest {
lock.notifyAll()
}
sslServerSocket.close()
} catch (ex: Throwable) {
} catch (ex: Exception) {
serverError = true
}
}

View File

@ -493,8 +493,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val republishInterval = try {
networkMapClient.publish(signedNodeInfo)
heartbeatInterval
} catch (t: Throwable) {
log.warn("Error encountered while publishing node info, will retry again", t)
} catch (e: Exception) {
log.warn("Error encountered while publishing node info, will retry again", e)
// TODO: Exponential backoff? It should reach max interval of eventHorizon/2.
1.minutes
}

View File

@ -333,7 +333,7 @@ open class Node(configuration: NodeConfiguration,
log.info("Retrieved public IP from Network Map Service: $this. This will be used instead of the provided \"$host\" as the advertised address.")
}
retrievedHostName
} catch (ignore: Throwable) {
} catch (ignore: Exception) {
// Cannot reach the network map service, ignore the exception and use provided P2P address instead.
log.warn("Cannot connect to the network map service for public IP detection.")
null

View File

@ -190,7 +190,7 @@ open class NodeStartup : NodeStartupLogging {
node.startupComplete.then {
try {
InteractiveShell.runLocalShell(node::stop)
} catch (e: Throwable) {
} catch (e: Exception) {
logger.error("Shell failed to start", e)
}
}

View File

@ -232,6 +232,7 @@ class RPCServer(
log.error("Failed to send message, kicking client. Message was ${job.message}", throwable)
serverControl!!.closeConsumerConnectionsForAddress(job.clientAddress.toString())
invalidateClient(job.clientAddress)
if (throwable is VirtualMachineError) throw throwable
}
}

View File

@ -94,8 +94,8 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
override fun run() {
val nextScheduleDelay = try {
updateNetworkMapCache()
} catch (t: Throwable) {
logger.warn("Error encountered while updating network map, will retry in $defaultRetryInterval", t)
} catch (e: Exception) {
logger.warn("Error encountered while updating network map, will retry in $defaultRetryInterval", e)
defaultRetryInterval
}
// Schedule the next update.

View File

@ -219,9 +219,13 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
val result = logic.call()
suspend(FlowIORequest.WaitForSessionConfirmations, maySkipCheckpoint = true)
Try.Success(result)
} catch (throwable: Throwable) {
logger.info("Flow threw exception... sending it to flow hospital", throwable)
Try.Failure<R>(throwable)
} catch (t: Throwable) {
if(t is VirtualMachineError) {
logger.error("Caught unrecoverable error from flow. Forcibly terminating the JVM, this might leave resources open, and most likely will.", t)
Runtime.getRuntime().halt(1)
}
logger.info("Flow raised an error... sending it to flow hospital", t)
Try.Failure<R>(t)
}
val softLocksId = if (hasSoftLockedStates) logic.runId.uuid else null
val finalEvent = when (resultOrError) {
@ -373,8 +377,8 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
maySkipCheckpoint = skipPersistingCheckpoint,
fiber = this.checkpointSerialize(context = serializationContext.value)
)
} catch (throwable: Throwable) {
Event.Error(throwable)
} catch (exception: Exception) {
Event.Error(exception)
}
// We must commit the database transaction before returning from this closure otherwise Quasar may schedule

View File

@ -43,6 +43,7 @@ import net.corda.nodeapi.internal.persistence.wrapWithDatabaseTransaction
import net.corda.serialization.internal.CheckpointSerializeAsTokenContextImpl
import net.corda.serialization.internal.withTokenContext
import org.apache.activemq.artemis.utils.ReusableLatch
import org.apache.logging.log4j.LogManager
import rx.Observable
import rx.subjects.PublishSubject
import java.security.SecureRandom
@ -135,7 +136,13 @@ class SingleThreadedStateMachineManager(
val fibers = restoreFlowsFromCheckpoints()
metrics.register("Flows.InFlight", Gauge<Int> { mutex.content.flows.size })
Fiber.setDefaultUncaughtExceptionHandler { fiber, throwable ->
(fiber as FlowStateMachineImpl<*>).logger.warn("Caught exception from flow", throwable)
if (throwable is VirtualMachineError) {
(fiber as FlowStateMachineImpl<*>).logger.error("Caught unrecoverable error from flow. Forcibly terminating the JVM, this might leave resources open, and most likely will.", throwable)
LogManager.shutdown(true)
Runtime.getRuntime().halt(1)
} else {
(fiber as FlowStateMachineImpl<*>).logger.warn("Caught exception from flow", throwable)
}
}
serviceHub.networkMapCache.nodeReady.then {
logger.info("Node ready, info: ${serviceHub.myInfo}")
@ -606,7 +613,7 @@ class SingleThreadedStateMachineManager(
private fun deserializeCheckpoint(serializedCheckpoint: SerializedBytes<Checkpoint>): Checkpoint? {
return try {
serializedCheckpoint.checkpointDeserialize(context = checkpointSerializationContext!!)
} catch (exception: Throwable) {
} catch (exception: Exception) {
logger.error("Encountered unrestorable checkpoint!", exception)
null
}

View File

@ -39,7 +39,7 @@ class TransitionExecutorImpl(
for (action in transition.actions) {
try {
actionExecutor.executeAction(fiber, action)
} catch (exception: Throwable) {
} catch (exception: Exception) {
contextTransactionOrNull?.close()
if (transition.newState.checkpoint.errorState is ErrorState.Errored) {
// If we errored while transitioning to an error state then we cannot record the additional

View File

@ -77,8 +77,8 @@ class FiberDeserializationChecker {
is Job.Check -> {
try {
job.serializedFiber.checkpointDeserialize(context = checkpointSerializationContext)
} catch (throwable: Throwable) {
log.error("Encountered unrestorable checkpoint!", throwable)
} catch (exception: Exception) {
log.error("Encountered unrestorable checkpoint!", exception)
foundUnrestorableFibers = true
}
}

View File

@ -276,7 +276,7 @@ class NodeVaultServiceTest {
assertThat(vaultService.queryBy<Cash.State>(criteriaByLockId1).states).hasSize(3)
}
println("SOFT LOCK STATES #1 succeeded")
} catch (e: Throwable) {
} catch (e: Exception) {
println("SOFT LOCK STATES #1 failed")
} finally {
countDown.countDown()
@ -292,7 +292,7 @@ class NodeVaultServiceTest {
assertThat(vaultService.queryBy<Cash.State>(criteriaByLockId2).states).hasSize(3)
}
println("SOFT LOCK STATES #2 succeeded")
} catch (e: Throwable) {
} catch (e: Exception) {
println("SOFT LOCK STATES #2 failed")
} finally {
countDown.countDown()

View File

@ -327,7 +327,7 @@ class TLSAuthenticationTests {
lock.notifyAll()
}
sslServerSocket.close()
} catch (ex: Throwable) {
} catch (ex: Exception) {
serverError = true
}
}

View File

@ -65,7 +65,7 @@ class InterestRateSwapAPI {
return try {
rpc.startFlow(AutoOfferFlow::Requester, newDeal).returnValue.getOrThrow()
ResponseEntity.created(URI.create(generateDealLink(newDeal))).build()
} catch (ex: Throwable) {
} catch (ex: Exception) {
logger.info("Exception when creating deal: $ex", ex)
ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.toString())
}

View File

@ -14,6 +14,7 @@ import org.apache.qpid.proton.amqp.UnsignedInteger
import org.apache.qpid.proton.codec.Data
import java.io.InputStream
import java.io.NotSerializableException
import java.lang.Exception
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.TypeVariable
@ -100,8 +101,8 @@ class DeserializationInput constructor(
throw NotSerializableException(amqp.mitigation)
} catch (nse: NotSerializableException) {
throw nse
} catch (t: Throwable) {
throw NotSerializableException("Internal deserialization failure: ${t.javaClass.name}: ${t.message}").apply { initCause(t) }
} catch (e: Exception) {
throw NotSerializableException("Internal deserialization failure: ${e.javaClass.name}: ${e.message}").apply { initCause(e) }
} finally {
objectHistory.clear()
}

View File

@ -21,7 +21,6 @@ include 'experimental'
include 'experimental:avalanche'
include 'experimental:behave'
include 'experimental:quasar-hook'
include 'experimental:kryo-hook'
include 'experimental:corda-utils'
include 'experimental:notary-raft'
include 'experimental:notary-bft-smart'

View File

@ -167,8 +167,8 @@ class DriverTests {
fun `driver waits for in-process nodes to finish`() {
fun NodeHandle.stopQuietly() = try {
stop()
} catch (t: Throwable) {
t.printStackTrace()
} catch (e: Exception) {
e.printStackTrace()
}
val handlesFuture = openFuture<List<NodeHandle>>()

View File

@ -95,8 +95,8 @@ fun <A> poll(
} else {
executorService.schedule(this, pollInterval.toMillis(), TimeUnit.MILLISECONDS)
}
} catch (t: Throwable) {
resultFuture.setException(t)
} catch (e: Exception) {
resultFuture.setException(e)
}
}
}

View File

@ -65,6 +65,9 @@ class ShutdownManager(private val executorService: ExecutorService) {
it.value()
} catch (t: Throwable) {
log.warn("Exception while calling a shutdown action, this might create resource leaks", t)
if (t is VirtualMachineError) {
throw t
}
}
is Try.Failure -> log.warn("Exception while getting shutdown method, disregarding", it.exception)
}

View File

@ -86,8 +86,8 @@ fun startPublishingFixedRateInjector(
)
}
}
} catch (throwable: Throwable) {
throwable.printStackTrace()
} catch (e: Exception) {
e.printStackTrace()
}
}
}

View File

@ -110,7 +110,7 @@ class BlobInspector : CordaCliWrapper("blob-inspector", "Convert AMQP serialised
} else {
null // Not an AMQP blob.
}
} catch (t: Throwable) {
} catch (e: Exception) {
return null // Failed to parse in some other way.
}
}

View File

@ -106,10 +106,10 @@ data class LoadTest<T, S>(
log.info("Executing $it")
try {
nodes.execute(it)
} catch (exception: Throwable) {
val diagnostic = executeDiagnostic(state, newState, it, exception)
} catch (throwable: Throwable) {
val diagnostic = executeDiagnostic(state, newState, it, throwable)
log.error(diagnostic)
throw Exception(diagnostic)
throw if (throwable is Exception) Exception(diagnostic) else throwable
}
}
}

View File

@ -30,13 +30,13 @@ class DockerInstantiator(private val volume: LocalVolume,
try {
localClient.killContainerCmd(container.id).exec()
LOG.info("Found running container: $instanceName killed")
} catch (e: Throwable) {
} catch (e: Exception) {
//container not running
}
try {
localClient.removeContainerCmd(container.id).exec()
LOG.info("Found existing container: $instanceName removed")
} catch (e: Throwable) {
} catch (e: Exception) {
//this *only* occurs of the container had been previously scheduled for removal
//but did not complete before this attempt was begun.
}

View File

@ -1,5 +1,6 @@
package net.corda.bootstrapper.nodes
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import net.corda.bootstrapper.Constants
import net.corda.core.utilities.contextLogger
@ -11,7 +12,7 @@ class NodeFinder(private val scratchDir: File) {
return scratchDir.walkBottomUp().filter { it.name == "node.conf" && !it.absolutePath.contains(Constants.BOOTSTRAPPER_DIR_NAME) }.map {
try {
ConfigFactory.parseFile(it) to it
} catch (t: Throwable) {
} catch (e: ConfigException) {
null
}
}.filterNotNull()

View File

@ -1,5 +1,6 @@
package net.corda.bootstrapper.notaries
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigFactory
import net.corda.bootstrapper.Constants
import net.corda.bootstrapper.nodes.FoundNode
@ -12,7 +13,7 @@ class NotaryFinder(private val dirToSearch: File) {
.map {
try {
ConfigFactory.parseFile(it) to it
} catch (t: Throwable) {
} catch (e: ConfigException) {
null
}
}.filterNotNull()

View File

@ -45,7 +45,7 @@ class AzureSmbVolume(private val azure: Azure, private val resourceGroup: Resour
cloudFileShare.createIfNotExists()
networkParamsFolder.createIfNotExists()
break
} catch (e: Throwable) {
} catch (e: Exception) {
LOG.debug("storage account not ready, waiting")
Thread.sleep(5000)
}