Repository navigation
fix(server): run onInitialized callbacks registered after initialization #1019
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
Changes from all commits
7cf7eb7
51124e6
d59c420
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,8 +31,12 @@ import io.modelcontextprotocol.kotlin.sdk.types.SUPPORTED_PROTOCOL_VERSIONS | |
| import io.modelcontextprotocol.kotlin.sdk.types.SetLevelRequest | ||
| import kotlinx.atomicfu.AtomicRef | ||
| import kotlinx.atomicfu.atomic | ||
| import kotlinx.atomicfu.getAndUpdate | ||
| import kotlinx.collections.immutable.PersistentList | ||
| import kotlinx.collections.immutable.persistentListOf | ||
| import kotlinx.coroutines.CompletableDeferred | ||
| import kotlinx.serialization.json.JsonObject | ||
| import kotlin.coroutines.cancellation.CancellationException | ||
| import kotlin.uuid.ExperimentalUuidApi | ||
| import kotlin.uuid.Uuid | ||
|
|
||
|
|
@@ -64,7 +68,12 @@ public open class ServerSession( | |
| @OptIn(ExperimentalUuidApi::class) | ||
| public val sessionId: String = Uuid.random().toString() | ||
|
|
||
| private var _onInitialized: (() -> Unit) = {} | ||
| /** | ||
| * Callbacks waiting for `notifications/initialized`, or `null` once it has been received. | ||
| * A callback registered after that point runs immediately instead of being stored. | ||
| */ | ||
| private val pendingInitializedCallbacks: AtomicRef<PersistentList<() -> Unit>?> = | ||
| atomic(persistentListOf()) | ||
|
|
||
| private var _onClose: () -> Unit = {} | ||
|
|
||
|
|
@@ -94,7 +103,7 @@ public open class ServerSession( | |
| handleInitialize(request) | ||
| } | ||
| setNotificationHandler<InitializedNotification>(Defined.NotificationsInitialized) { | ||
| _onInitialized() | ||
| pendingInitializedCallbacks.getAndSet(null)?.forEach { callback -> callback() } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking: the flush loop has no per-callback error isolation, unlike the immediate path added in Failure scenario: two components register Suggested fix: route both paths through one guarded helper, so a failing callback cannot drop the others: private fun runInitializedCallback(block: () -> Unit) {
try {
block()
} catch (e: CancellationException) {
throw e
} catch (cause: Throwable) {
logger.error(cause) { "Error in onInitialized callback" }
onError(cause)
}
}and call it from this |
||
| CompletableDeferred(Unit) | ||
| } | ||
|
|
||
|
|
@@ -120,12 +129,27 @@ public open class ServerSession( | |
| * | ||
| * The callback must be synchronous and fast: it runs on the message-dispatch path for | ||
| * `notifications/initialized`, after concurrent dispatch has been enabled for the session. | ||
| * | ||
| * If initialization has already completed, [block] runs immediately on the calling thread. | ||
| * Otherwise it runs when `notifications/initialized` is received, after the callbacks | ||
| * registered before it. Each callback runs at most once. | ||
| * | ||
| * An exception thrown by [block] is reported to [onError] instead of being propagated, | ||
| * so it does not prevent other callbacks from running. | ||
| */ | ||
| public fun onInitialized(block: () -> Unit) { | ||
| val old = _onInitialized | ||
| _onInitialized = { | ||
| old() | ||
| block() | ||
| val callback = { | ||
| try { | ||
| block() | ||
| } catch (e: CancellationException) { | ||
| throw e | ||
| } catch (cause: Throwable) { | ||
| logger.error(cause) { "Error in onInitialized callback" } | ||
| onError(cause) | ||
| } | ||
| } | ||
| if (pendingInitializedCallbacks.getAndUpdate { it?.adding(callback) } == null) { | ||
| callback() | ||
| } | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.