dapr v1.18.3 版本更新介绍
发布日期: 2026-08-14
版本号: v1.18.3
Dapr 1.18.3版本包含多项错误修复,主要涉及核心稳定性、工作流可靠性、组件交互及资源管理。修复内容包括:支持Actor状态存储组件的热重载,解决了因单个sidecar断连导致命名空间内所有sidecar与Placement断开的问题,修复了Scheduler在关闭时挂起及作业无法重新投递的问题,修正了含时区前缀的作业调度导致的崩溃或静默忽略。工作流方面,修复了运行时状态查询间歇失败、
continue_as_new迭代共享无界追踪、最后一个worker断连后工作流永久卡住、孤立活动结果提醒无限重试以及终止命令被静默丢弃的问题。组件交互上,解决了应用响应订阅探测缓慢时输入绑定未激活、MCPServer启动时端点短暂不可用导致不可用、HTTP投递含非字符串追踪字段的pub/sub消息时崩溃、向gRPC应用投递pub/sub消息因“关闭的网络连接”失败,以及Azure组件认证在SPIFFE凭证处停止而不回退的问题。此外,还修复了Scheduler内嵌etcd指标从监控端点消失的问题。
更新内容 (中文)
Dapr 1.18.3
此更新包含以下错误修复:
- Actor状态存储组件无法热重载
- 当单个 sidecar 在信息传播过程中断开连接时,命名空间中的所有 sidecar 均与 Placement 断开连接
- Scheduler 嵌入式 etcd 指标从指标端点丢失
- Scheduler 在关闭时挂起,并在客户端断开连接后未能重新传递作业
- 调度具有格式错误的带时区前缀计划的作业时 Scheduler 崩溃
- 时区前缀在
@every作业计划上被静默忽略 - 工作流运行期间 GetInstance 间歇性失败
- 当应用对订阅发现探测响应较慢时,输入绑定未激活
- MCPServer 在其端点启动时短暂不可用后变得不可用
- 通过 HTTP 传递具有非字符串跟踪字段的 pub/sub 消息时崩溃
- 工作流
continue_as_new迭代共享单个无界跟踪 - 最后一个工作流工作者断开连接后,停滞的工作流永久卡住
- 修复了孤立的工作流 activity-result 提醒永远重试的问题
- 向 gRPC 应用程序传递 pub/sub 消息时出现 “use of closed network connection” 失败
- 当工作流终止与其他事件在同一批次中传递时被静默丢弃
- Azure 组件身份验证在 SPIFFE 凭据处停止,而不是回退
Actor状态存储组件无法热重载
问题
启用热重载后,daprd 拒绝对已成为或将成为 actor 状态存储的状态存储组件进行任何更改。
创建、更新或删除此类组件会记录 Aborting to hot-reload a state store component that is used as an actor state store,更改被静默忽略,直到 daprd 重启。
特别是,没有 actor 状态存储启动的 daprd 永远无法获得工作 actor 主机或工作流 API:向状态存储组件添加 actorStateStore: \"true\" 没有效果,每个工作流操作都持续失败并显示 the state store is not configured to use the actor runtime。
影响
如果您依赖热重载来管理组件,并且需要在不重启 daprd 的情况下引入、替换或删除 actor 状态存储,则会受到影响;例如通过在运行的 sidecar 上将现有状态存储提升为 actor 状态存储来启用工作流。
根本原因
当 actor 子系统不支持在运行时更改其状态存储时,添加了此限制:存储名称在启动时捕获一次,启用 actor 主机的决策在启动时确定一次,并且没有任何内容对存储后来出现、更改或消失做出反应。
解决方案
Actor 状态存储现在可以热重载,并且 actor 子系统在运行时协调与存储的主机关系:
- 添加 actor 状态存储(创建标记的组件,或标记现有状态存储)会启用 actor 主机:应用的 actor 类型和工作流 actor 类型将被通告到 Placement,并且 actor 和工作流 API 开始工作;包括在存储存在之前连接的工作流工作者。
- 移除 actor 状态存储(删除组件或取消标记)会在进程内关闭 actor 主机:托管的 actor 被排空和停用(应用接收停用通知),actor 类型从 Placement 和 Scheduler(daprd 保留作为 actor 客户端,仍然可以调用其他位置托管的 actor)中取消通告,并且 actor 状态和工作流 API 在再次配置存储之前返回
the state store is not configured to use the actor runtime。daprd 本身继续运行,并且当存储返回时,主机自动恢复,无需应用或工作流工作者重新连接。 - 就地更新 actor 状态存储组件(例如热重载获取组件引用的已轮换密钥)会交换存储实例,而不会中断 actor 主机:存储按调用解析,因此托管的 actor 和正在运行的工作流将继续使用相同的后备存储。排空保留用于被移除、取消标记或被不同名称的组件替换的存储。
此更改还附带两个进一步的一致性修复:
- Actor 类型现在仅在配置了 actor 状态存储时通告到 Placement。之前,声明了 actor 实体但完全没有 actor 状态存储的应用仍然会接收 actor 调用,尽管 actor 状态、提醒和工作流无法正常工作,这与文档要求和
actor hosting disabled日志相矛盾。 - 热加载第二个标记为 actor 状态存储的组件时,会跳过并记录错误日志(命名现有的 actor 状态存储),而不是重复项静默损坏组件存储簿记。
当单个 sidecar 在信息传播过程中断开连接时,命名空间中的所有 sidecar 均与 Placement 断开连接
问题
周期性地,Placement 服务器在同一毫秒内关闭命名空间中的每个 sidecar 流,并且所有 sidecar 同时重新连接。 重新连接时,每个 sidecar 会暂停其本地 actor,因此正在调用中的 actor 在完全正常的主机上被中断。 每次发生后,信息传播版本都从头开始重新启动。
影响
如果您运行 actor 主机 sidecar,尤其是在具有许多 sidecar 和频繁部署更新的命名空间中,则会受到影响。 在信息传播轮次进行中,每个 sidecar 断开连接都会使命名空间更接近大规模断开连接,因此大型、频繁更新的集群可能每隔几分钟就会看到命名空间中的每个 sidecar 被丢弃。 每次都会中断未受影响主机上的正在进行中的 actor 调用。
根本原因
单个失败的 Placement 流可能会报告两次关闭:一次来自其接收循环的展开,一次来自同一连接上的失败信息传播发送。 每个命名空间的连接计数器都会为这两个报告递减,每次此类事件后比实时连接的实际数量低一个。 一旦累积漂移达到零,命名空间的信息传播循环就在实时流仍然附加时被关闭,关闭所有流并重置信息传播版本。
解决方案
流关闭现在仅报告一次:失败的信息传播发送会取消流的上下文,将发送错误保留为关闭原因,并且接收循环的展开是报告关闭的唯一位置。 命名空间连接计数器现在始终与实时流的数量匹配,因此健康的命名空间永远不会因为一个 sidecar 的断开连接而被拆除。
Scheduler 嵌入式 etcd 指标从指标端点丢失
问题
升级到 1.18 后,所有嵌入式 etcd 指标(etcd_*,例如 etcd_server_has_leader、etcd_mvcc_db_total_size_in_bytes 和 etcd_disk_backend_commit_duration_seconds_bucket)从 Scheduler 的指标端点(默认端口 9090)消失。
Dapr 自己的 dapr_scheduler_* 指标未受影响。
影响
如果您抓取 Scheduler 指标端点以获取嵌入式 etcd 指标(例如填充 dapr-scheduler Grafana 仪表板的嵌入式 etcd 面板或对 etcd 数据库大小和磁盘提交延迟进行告警),则会受到影响。
这些序列在从 1.17 升级到 1.18 后停止发出。
根本原因
在 1.18 中,所有 Dapr 二进制文件共享的指标导出器从服务于进程全局默认 Prometheus 注册表切换到全新的私有注册表,以避免在导出器在同一进程中重启时重复收集器注册导致 panic。 嵌入式 etcd 在包初始化期间在其全局默认注册表上注册其所有收集器,因此其指标从未写入端点服务的私有注册表,并且无处暴露。
解决方案
指标端点现在从私有注册表和全局默认注册表中收集数据。
导出器本身从未在默认注册表上注册任何内容,因此进程内重启仍然是安全的,并且原始 panic 不会再次发生。
所有 etcd_* 指标再次在 Scheduler 指标端点上发出,并且链接库(例如 grpc_server_* 和 grpc_client_*)在默认注册表上注册的其他指标在所有 Dapr 二进制文件中恢复到 1.17 的行为。
Scheduler 在关闭时挂起,并在客户端断开连接后未能重新传递作业
问题
Scheduler 中的两个相关故障模式:
- Scheduler 实例可能在关闭期间永远挂起,成为僵尸进程:其 gRPC 传输保持活动状态,其健康端点持续返回 200,但没有处理程序在提供服务。 连接的 daprd sidecar 在进行中的调用上挂起,而不是故障转移到僵尸。
- 在 daprd sidecar 突然断开连接后,Scheduler 可能会停止为该 sidecar 的整个命名空间传递作业和 actor 提醒,在某些情况下持续几分钟,即使其他健康的 sidecar 仍保持连接。
影响
在负载下 Scheduler 重启或崩溃可能会升级为集群范围的停机窗口:作业和 actor 提醒传递停滞,直到 pod 被手动重启或客户端重新连接并重新注册。 如果您在 HA 中运行 Scheduler 并具有活动连接的 sidecar,尤其是在高作业或工作流吞吐量下,则会受到影响。
根本原因
关闭使用了无界的 gRPC GracefulStop(),它等待每个打开的流。
一个客户端已连接但从未发送或已停止读取的 WatchJobs 流,使其处理程序在其初始 Recv(或流控制阻塞的 Send)中阻塞,因此排空永远不会完成,进程永远不会退出。
就绪状态仅在排空完成后失败,因此健康探测从未检测到僵尸。
另外,一个死亡的 WatchJobs 流会发出重复的关闭事件(一个来自其接收循环,加上每个失败发送一个),而每个命名空间的连接计数器针对每个事件递减一次。
重复项可能在实时流仍然存在时将计数驱动为零,删除命名空间并丢弃其中的每个流和可传递的作业前缀。
失败的发送也会保留其作业的完成回调,直到流被完全收割,因此调度引擎无法立即将这些作业重新传递给健康的流。
连接事件循环对象回收中的数据竞争还可能在连接不稳定时损坏循环状态。
解决方案
Scheduler 关闭现在在排空之前使就绪状态失败,并将优雅排空限制在 5 秒内,然后强制 gRPC 服务器停止,从而保证进程退出。 死亡流现在仅关闭一次:关闭在检测时取消流,立即注销其可传递的前缀,并将进行中的作业解析为不可传递,以便引擎立即将它们重新传递给健康的流。 命名空间删除现在由拥有权威流集的连接跟踪循环确认,因此重复或杂散的关闭事件无法再拆除仍具有实时流的命名空间。 事件循环对象回收竞争已消除。
调度具有格式错误的带时区前缀计划的作业时 Scheduler 崩溃
问题
调度一个 schedule 带有时区前缀(TZ= 或 CRON_TZ=)且其后没有计划的作业(例如 TZ=UTC)会崩溃 Scheduler 进程,而不是返回错误。
影响
任何被允许调度作业的客户端都可能使用单个格式错误的计划字符串使共享 Scheduler 崩溃。 daprd 不验证计划;它逐字将其转发给 Scheduler,Scheduler 对其进行解析,并且 Scheduler 的 gRPC 服务器没有 panic 恢复拦截器,因此解析 panic 终止了进程。 在高可用性部署中,这表现为每当有问题的作业(重新)加载时,Scheduler pod 就会崩溃循环。
根本原因
cron 解析器使用 strings.Index(spec, \" \") 定位时区前缀和计划之间的边界,并在结果上切片字符串,而不检查 -1。
没有后续计划的前缀产生 -1 的索引,导致越界切片 panic。
解决方案
cron 解析器(来自 github.com/dapr/kit)现在使用任何空白字符查找边界,并在时区前缀后没有计划时返回描述性错误。
格式错误的计划现在在作业调度时被拒绝,Scheduler 不再崩溃。
时区前缀在 @every 作业计划上被静默忽略
问题
将时区前缀与 @every 间隔组合的 schedule(例如 CRON_TZ=Europe/Rome @every 1h)被接受,但时区被静默丢弃。
影响
该前缀看起来将作业固定到时区,但 @every 计划以固定间隔触发,没有可应用时区的挂钟时间。
设置 CRON_TZ=Europe/Rome @every 24h 以期望“每天在同一本地时间”的用户会看到触发时间在夏令时转换时偏移一小时——这正是时区前缀似乎要防止的问题。
根本原因
@every 计划解析为固定常量延迟计划,不携带位置,因此解析的时区被丢弃而不是应用。
解决方案
cron 解析器(来自 github.com/dapr/kit)现在在作业调度时拒绝 @every 计划上的时区前缀,因此错误配置会立即显现,而不是静默产生错误的触发时间。
要将循环作业固定到时区中的挂钟时间,请改用 cron 表达式,例如 CRON_TZ=Europe/Rome 0 0 9 * * *。
工作流运行期间 GetInstance 间歇性失败
问题
只读工作流状态查询(GetInstance / GetWorkflowMetadata,或等效的 SDK 调用(如 GetWorkflowStateAsync))可能在查询的工作流正常运行时间歇性失败,gRPC Unknown 错误:
Status(StatusCode=\"Unknown\", Detail=\"workflow '<id>': inbox key 'inbox-000000' declared in metadata (inboxLength=1) but missing from state store (transient store read failure or partial save?)\")
该故障是瞬时且自愈的:对同一实例的下一次轮询成功,并且工作流本身成功完成。
影响
如果您在工作流取得进展时轮询工作流状态,则会受到影响,在高活动并发性下(例如由多个并发客户端轮询的多个并行活动的扇出)最为明显,尽管单个活动转换也可能触发它。 调用者收到看似终止性错误的健康工作流,因此没有自己的重试逻辑的客户端会呈现虚假故障。 持久化的工作流状态实际上从未不一致。
根本原因
加载工作流状态在两个单独的状态存储调用中读取 metadata 行和 inbox-*/history-* 条目行,并且状态查询路径在不持有工作流 actor 锁的情况下执行这些读取。
工作流 actor 保存是单个原子事务,它删除已消耗的收件箱条目并写入更新的元数据,因此在读取者的两次调用之间提交的保存会产生撕裂读取:旧的元数据仍然声明了第二个读取不再找到的收件箱条目。
此撕裂读取作为硬错误报告给调用者。
解决方案
工作流状态加载现在检测此不匹配,并使用新读取的元数据重试整个加载,最多 5 次尝试,间隔 15 毫秒。
在尝试之间比较元数据 ETag:更改的 ETag 证明在读取之间发生了并发保存(重试),而未更改的 ETag 证明条目确实从存储中丢失,在这种情况下仍会返回原始错误。
与活动工作流竞争的状态查询现在返回一致的结果,而不是瞬时 Unknown 错误。
当应用对订阅发现探测响应较慢时,输入绑定未激活
问题
在开始从输入绑定读取之前,daprd 会询问应用程序是否订阅该绑定:对绑定路由的 HTTP OPTIONS 请求,或 gRPC ListInputBindings 调用。
此请求有硬编码的 3 秒预算,无法更改。
在 3 秒内未完成预热的应用程序从未及时响应,因此 daprd 将绑定视为未订阅,并且从未激活它。
影响
如果您的应用程序在启动后提供第一个请求较慢(JVM 或 JIT 预热、大型依赖注入图或资源受限节点),并且您声明了输入绑定而没有明确的 direction: input 元数据条目,则会受到影响。
绑定组件本身正确初始化并出现在 sidecar 的元数据端点中,因此即使从未传递事件,组件看起来也是健康的。
在 HTTP 通道上,失败的探测也会中止剩余的绑定,使该 sidecar 上的每个输入绑定都处于非活动状态;唯一的跟踪是 sidecar 日志中的 failed to read from bindings 警告。
在 gRPC 通道上,探测失败是静默的。
根本原因
订阅发现截止时间在绑定处理器中硬编码为 3 秒,并从后台上下文构建。 它既无法为启动缓慢的应用程序调整,也无法在运行时关闭时取消,而此时探测仍在进行中。
解决方案
超时现在可通过新的 daprd --app-binding-options-timeout 标志配置,该标志适用于 HTTP OPTIONS 探测和 gRPC ListInputBindings 探测。
默认值仍为 3 秒,因此现有部署保持不变,非正值回退到该默认值。
探测现在派生自运行时的上下文,因此在关闭时会立即取消,而不是运行到完整的截止时间。
在绑定组件上设置 direction: input 会继续完全跳过探测。
MCPServer 在其端点启动时短暂不可用后变得不可用
问题
加载 MCPServer 资源会连接到 MCP 服务器并列出其工具,这正是安装该服务器的 dapr.internal.mcp.<name>.ListTools 和 .CallTool.<tool> 工作流并注册它们运行的工作流 actor 主机的方式。
如果该连接失败,则失败是最终的:在 sidecar 进程的生命周期内永远不会重新尝试注册。
影响
如果在 daprd 加载 MCPServer 时其端点未提供服务,则会受到影响,例如来源仍在启动中、其前面的代理或隧道在后端启动时返回 502,或 DNS 尚未传播。 资源仍出现在元数据 API 中,因此看起来已加载,但其任何工作流都不存在。 读取元数据并调度其中一个工作流的客户端找不到工作流 actor 类型的主机,并且工作流创建会无限期重试缺失的主机,因此调用会挂起直到调用者的上下文过期,而不是失败。 对于无界上下文,它根本不会返回。 恢复需要编辑资源以触发热重载,或重新启动 sidecar。
根本原因
每次资源加载仅调用一次注册,没有重试,因此瞬时连接失败与永久无法到达的端点无法区分。 两者都使服务器在元数据中列出且无法使用。
解决方案
MCPServer 注册现在在 Dapr 的内置初始化重试策略下运行,该策略与其他地方用于资源初始化的策略相同:指数退避,从 500ms 开始,在 10 秒预算内最多重试 3 次。
在该窗口内变得可访问的端点会正常注册,并且其工具按预期工作。
真正无法访问的端点在重试耗尽后表现如前,ignoreErrors: true 继续保持 daprd 运行。
通过 HTTP 传递具有非字符串跟踪字段的 pub/sub 消息时崩溃
问题
通过 HTTP 将 pub/sub 消息传递给订阅者时,当消息的 CloudEvent 携带非字符串 traceparent 或 traceid 字段(例如 JSON 数字、布尔值或对象而不是字符串)时,会导致 daprd 进程崩溃。
影响
任何被允许发布到已订阅主题的客户端都可能使用单个消息使 sidecar 崩溃。
发布者控制 CloudEvent 跟踪字段:使用内容类型 application/cloudevents+json 和正文(如 {\"specversion\":\"1.0\", ..., \"traceid\":12345})发布会将非字符串值一直保留到传递。
HTTP 传递路径在后台 goroutine 中运行,没有 panic 恢复,因此故障会终止整个进程,而不是丢弃单个消息。
gRPC 传递路径未受影响。
在高可用性部署中,这表现为当有问题的消息被重新传递时 sidecar 崩溃循环。
根本原因
HTTP pub/sub 传递路径(pkg/runtime/subscription/postman/http 中的 Deliver 和 DeliverBulk)从 CloudEvent 读取跟踪字段并执行未检查的类型断言为 string。
CloudEvent 从发布者控制的字节反序列化为 map[string]any,因此非字符串跟踪字段变为 float64、bool 或 map,并且断言 panic。
解决方案
HTTP 传递路径现在使用检查的类型断言,与 gRPC 传递路径匹配:非字符串跟踪字段被忽略(跳过该消息的跟踪),而不是使进程崩溃。 携带格式错误跟踪字段的消息现在正常传递给订阅者。
工作流 continue_as_new 迭代共享单个无界跟踪
问题
当工作流使用 continue_as_new 重启自身时,新迭代继承前一个迭代的跟踪上下文,而不是开始自己的跟踪。
因此,每个无限循环工作流都加入第一个迭代的跟踪,无限期共享一个跟踪 ID。
影响
如果您在启用分布式跟踪(OTLP 或 Zipkin 导出器到任何后端,例如 Jaeger、Azure Monitor 或 Geneva)的情况下运行基于 continue_as_new 构建的无限或轮询工作流,则会受到影响。
在数小时或数天内迭代的工作流会产生单个跟踪,累积数千个跨度。
未启用跟踪运行的工作流不受影响。
根本原因
在 continue_as_new 完成时,持久任务引擎通过逐字复制前一个迭代的父跟踪上下文来构建新迭代的执行启动事件。
因此,每个迭代的编排跨度都源自原始跟踪,并且跟踪无限增长。
解决方案
当先前的迭代被跟踪时,每个 continue_as_new 转换现在为新迭代生成一个新的 W3C 根跟踪上下文(新的随机跟踪和跨度 ID),因此每个迭代都产生自己的有界、可独立查询的跟踪。
应用程序内创建的活动跨度和跨度继续如前所述加入其自己的迭代跟踪。
未被跟踪的工作流在转换后保持未跟踪状态。
最后一个工作流工作者断开连接后,停滞的工作流永久卡住
问题
进入 STALLED 状态(版本不可用、补丁不匹配或有效负载大小超过)的工作流可能在最后一个连接的工作流工作者在工作流停滞时断开连接而永久卡住。
重新连接的工作者(包括注册停滞等待的确切工作流版本的工作者)也无法恢复它:状态查询继续报告 STALLED,唯一的恢复方法是重启 daprd。
发生这种情况时,sidecar 记录:
error while disconnecting work item stream: failed to deactivate workflow '<id>': actor is stalled
影响
停滞的存在是为了使工作流在工作者离开后生存,并在有能力的工作者返回时恢复,最常见的是滚动升级,其中旧的应用程序版本断开连接,新版本重新连接。
如果您使用工作流版本控制、补丁或配置的 --max-body-size,并且应用程序的所有工作流工作者在工作流停滞时断开连接(例如在应用程序重启、滚动升级、缩减为零期间),则会受到影响。
根本原因
停滞的工作流 actor 将其执行暂停在进程内,持有执行提醒直到其上下文被取消,并将其锁标记为停滞。
当最后一个工作流工作者断开连接时,daprd 注销工作流 actor 类型并停用所有工作流 actor,但停用从获取 actor 的锁开始,而锁在 actor 停滞时拒绝获取。
工作流以后是否能够恢复取决于竞争:如果调度器的提醒流拆除在停用到达 actor 之前取消了暂停的执行,停用成功,并且当工作者重新连接时,未确认的提醒被重新传递。
如果停用赢得了竞争,则失败并显示 actor is stalled,使 actor 保持激活状态并持有提醒,因此重新连接的工作者没有任何内容可重新传递,工作流从未重新执行。
解决方案
停用停滞的工作流 actor 现在唤醒暂停的执行,而不是失败:持有的执行立即返回,留下未确认的执行提醒,停用完成。
当工作流工作者重新连接时,提醒被重新传递,工作流重新执行,一旦连接的工作者满足停滞条件(例如注册了所需的工作流版本,或 daprd 使用更大的 --max-body-size 重启),就会恢复并完成。
从停滞状态恢复不再依赖于计时,并且不再需要 daprd 重启。
修复了孤立的工作流 activity-result 提醒永远重试的问题
问题
当工作流活动完成但其父工作流 actor 不可达时(例如在 Placement 重新平衡或主机重启期间),活动 actor 将结果作为 activity-result 提醒在 workflow actor 上持久排队,以便在工作流再次可及时交付结果。
如果工作流实例在提醒触发之前被清除,提醒就会成为孤立状态:它指向一个不再存在的实例。
影响
此问题影响在 Dapr 1.18.0-1.18.2 上运行具有活动的工作流(其中实例被清除(显式或通过状态保留策略),而活动结果仍在传输中)的用户,特别是在 Placement 不稳定(例如滚动重启或缩放事件)的情况下。 每个孤立的提醒为托管工作流 actor 类型的 daprd 增加每秒一次的永久调用负载。
根本原因
工作流 actor 的 activity-result 提醒处理程序将实例未找到错误转发给提醒系统,而没有将其分类为终止。
调度器将任何错误视为失败调用并应用提醒的失败策略,对于此提醒类型,是每秒一次的恒定重试,没有重试限制。
解决方案
activity-result 提醒处理程序现在将实例未找到视为成功的传递结果:对于活动实例,至少一次传递保持不变,因为此修复仅影响目标实例已被清除的提醒。
向 gRPC 应用程序传递 pub/sub 消息时出现 “use of closed network connection” 失败
问题
通过 gRPC 连接的应用程序间歇性地在应用程序看到消息之前失败,并且 sidecar 记录:
error returned from app while processing pub/sub event <id>: retriable error occurred: rpc error: code = Unavailable desc = connection error: desc = \"error reading server preface: read tcp 127.0.0.1:56264->127.0.0.1:14208: use of closed network connection\"
应用程序在整个过程中是健康且正在监听的,并且相同的消息在以后尝试或 sidecar 重启后成功传递。
影响
如果您运行使用 --app-protocol grpc(或 grpcs)订阅主题的应用程序,并且已升级到 1.18,则会受到影响。
故障在接收零星流量的订阅上以及应用程序接受新连接较慢的主机上(例如繁忙的线程池或垃圾收集暂停)最为明显。
每个受影响的传递都被报告为可重试失败,因此消息要么由代理重新传递(RabbitMQ、Kafka 和其他具有重新传递功能的组件),要么在没有该功能的组件上被丢弃。
相同的连接处理用于服务调用到 gRPC 应用程序、输入绑定传递和作业触发器,因此这些路径可能以相同的方式失败。
使用 --app-protocol http 的应用程序未受影响。
根本原因
daprd 使用 MinConnectTimeout 为一秒来拨号应用程序。
在 gRPC 中,该值是整个连接尝试的预算,涵盖 TCP 连接和 HTTP/2 握手,而不仅仅是 TCP 连接:当它到期时,gRPC 硬关闭套接字,而已经在该连接上的请求会因 error reading server preface: use of closed network connection 而失败。
直到 1.17,这几乎从未发生,因为 daprd 在进程的整个生命周期中保持单个应用程序连接,并且仅在启动时拨号。
在 1.18 中,应用程序连接移入用于 sidecar 到 sidecar 连接的同一池中,因此在传递消息时按需建立连接,并且每个新连接只有一秒钟的时间来完成握手,然后在其上传递的请求失败。
该池使其比必要的更频繁。该池被配置为保持一个到应用程序的热连接,但是一旦该连接闲置时间超过池的三分钟闲置窗口,它就既不会再发出(被视为过期)也不会关闭(它是被保持温暖的连接)。
它被困在池中,无法使用且打开,因此空闲期后到达的每个请求都拨号一个全新的连接,而不是重用温暖的连接。
解决方案
到应用程序的连接尝试现在给予 gRPC 的默认预算 20 秒,而不是一秒,与 daprd 用于 sidecar 到 sidecar 连接的设置相匹配。 这不会改变请求等待无法到达的应用程序的时间:被拒绝的连接仍然立即失败,并且请求等待的时间仍由调用者的上下文和弹性策略控制。 池持有的满足其热连接最小值的连接也不会过期。 启动时建立的应用程序连接现在在 sidecar 的生命周期内保持使用,因此每小时接收一条消息的订阅不再为每条消息重新拨号应用程序。
当工作流终止与其他事件在同一批次中传递时被静默丢弃
问题
终止工作流间歇性地没有效果:工作流的状态保持 RUNNING,并且它继续执行活动和计时器,就好像从未发出终止一样。
阻塞终止调用(例如 .NET SDK 中的 TerminateWorkflowAsync)永远不会返回,因为它们等待实例达到永远不会到来的终止状态。
启用调试日志记录时,丢弃的终止显示 ExecutionTerminated 事件被传递和消费而没有效果:
received work item with 2 new event(s): [ExecutionTerminated, TaskCompleted#1]
workflow execution returned with status 'ORCHESTRATION_STATUS_RUNNING'
在挂起时终止的工作流以相同的方式确定性地受到影响:状态保持 SUSPENDED,发送给它的每个终止都丢失了。
影响
如果您终止了正在积极取得进展的工作流,则会受到影响,与 SDK 语言无关。
窗口取决于收件箱压力:当终止到达与其他事件在同一批次工作项中且不是该批次的最后一个事件时,终止被丢弃,因此循环短活动和计时器的长时间运行的工作流(无尽的轮询器、监视器、continue_as_new 循环)最容易暴露,而空闲在单个慢活动或计时器上的工作流几乎总是单独接收其终止,并且不受影响。
丢弃的终止与其批次一起消费,永远不会重新传递,因此实例继续运行;重试的终止再次竞争相同的窗口。
丢弃的递归终止也永远不会级联,使子工作流作为孤儿运行。
对于挂起的工作流没有窗口:在恢复工作流之前,每个终止都丢失了。
根本原因
工作流引擎将实例的挂起事件作为一批交给工作流执行器,并完全依赖 SDK 执行器将 ExecutionTerminated 事件转换为终止结果。
SDK 执行器在处理事件时注册终止,但继续将批次中的其余事件馈送到工作流代码中,该代码恢复超过其自身终止并产生竞争结果:调度更多工作、正常完成或通过 continue_as_new 重启。
continue_as_new 结果总是丢弃终止;其他结果与其非确定性竞争。
在工作流挂起时传递的终止根本不产生结果,因为挂起抑制了执行器将返回的每个操作。
由于引擎信任执行器的结果,并且事件随批次一起消费,因此终止永久丢失。
解决方案
当传递的批次包含 ExecutionTerminated 事件且执行器未完成工作流时,daprd 丢弃注定要执行的挂起工作,并将实例完成为 TERMINATED。
与终止一起返回的 continue_as_new 不再启动新的迭代,并且终止挂起的工作流现在无需恢复即可终止它。
Azure 组件身份验证在 SPIFFE 凭据处停止,而不是回退
问题
Azure(Microsoft Entra ID)组件通过按顺序尝试凭据链进行身份验证,直到有一个成功。
当设置了 azureClientId 和 azureTenantId 时,该链包括 SPIFFE 工作负载身份凭据,并且如果没有任何 SPIFFE JWT SVID 源可用,则该链在此步骤停止:
ChainedTokenCredential: failed to acquire a token.
Attempted credentials:
ClientAssertionCredential: failed to get JWT SVID source from context
链中的后续凭据(例如托管标识或 Azure CLI)从未尝试,因此即使有可用的有效凭据,组件也未能进行身份验证。
影响
如果您在 Dapr 1.16.0 或更高版本(其中 SPIFFE 凭据加入默认链)中进行以下任一配置,则会受到影响:
- 具有设置了
azureClientId和azureTenantId但没有客户端密钥或证书的 Azure 组件,依赖于默认链中的后续凭据(例如托管标识或 Azure CLI)。 - 显式
azureAuthMethods列表将spiffeworkloadidentity放在另一种方法之前(例如spiffeworkloadidentity,managedidentity),期望在未配置 SPIFFE 时回退。
根本原因
ChainedTokenCredential 仅在报告 credentialUnavailableError 时继续通过凭据;任何其他错误都被视为致命错误并结束链。
SPIFFE 凭据在上下文不携带 JWT SVID 源时返回普通错误,因此缺少先决条件被视为致命身份验证错误,而不是尝试下一个凭据的信号。
解决方案
SPIFFE 凭据现在在发起任何令牌请求之前,当没有 JWT SVID 源存在时报告自己不可用,这正是 ChainedTokenCredential 要求继续到链中下一个凭据的。
默认链和显式排序的 azureAuthMethods 列表现在按预期回退,并且当配置了 SPIFFE 源时行为保持不变。
更新内容 (原始)
Dapr 1.18.3
This update contains the following bug fixes:
- Actor state store components cannot be hot reloaded
- All sidecars in a namespace disconnected from Placement when a single sidecar disconnected mid-dissemination
- Scheduler embedded etcd metrics missing from the metrics endpoint
- Scheduler hanging on shutdown and failing to redeliver jobs after client disconnects
- Scheduler crash when scheduling a job with a malformed timezone-prefixed schedule
- Timezone prefix silently ignored on
@everyjob schedules - Workflow GetInstance intermittently failing while the workflow is running
- Input bindings not activated when the app is slow to answer the subscription discovery probe
- MCPServer left unusable when its endpoint was briefly unavailable at startup
- Crash when delivering a pub/sub message with a non-string trace field over HTTP
- Workflow
continue_as_newiterations sharing a single unbounded trace - Stalled workflows permanently stuck after the last workflow worker disconnects
- Fixes orphaned workflow activity-result reminders retrying forever
- Pub/sub delivery to a gRPC app failing with “use of closed network connection”
- Workflow terminate silently dropped when delivered in the same batch as other events
- Azure component authentication halting at the SPIFFE credential instead of falling back
Actor state store components cannot be hot reloaded
Problem
With hot reloading enabled, daprd refused to apply any change to a state store component that is, or would become, the actor state store.
Creating, updating, or deleting such a component logged Aborting to hot-reload a state store component that is used as an actor state store and the change was silently ignored until daprd was restarted.
In particular, a daprd which started without an actor state store could never gain working actor hosting or Workflow APIs: adding actorStateStore: "true" to a state store component had no effect, and every workflow operation kept failing with the state store is not configured to use the actor runtime.
Impact
You were affected if you relied on hot reloading to manage components and needed to introduce, replace, or remove the actor state store without restarting daprd; for example enabling workflows on a running sidecar by promoting an existing state store to the actor state store.
Root Cause
The restriction was added when the actors subsystem had no support for its state store changing at runtime: the store name was captured once at startup, the decision to enable actor hosting was made once at startup, and nothing reacted to the store later appearing, changing, or disappearing.
Solution
The actor state store can now be hot reloaded, and the actors subsystem reconciles hosting with the store at runtime:
- Adding an actor state store (creating a marked component, or marking an existing state store) enables actor hosting: the app’s actor types and the workflow actor types are advertised to placement, and the actor and Workflow APIs start working; including for workflow workers that connected before the store existed.
- Removing the actor state store (deleting the component, or unmarking it) shuts down actor hosting in-process: hosted actors are drained and deactivated (the app receives deactivations), actor types are de-advertised from placement and the Scheduler (daprd remains an actor client and can still invoke actors hosted elsewhere), and actor state and Workflow APIs return
the state store is not configured to use the actor runtimeuntil a store is configured again. daprd itself keeps running, and hosting resumes automatically when a store returns, with no app or workflow-worker reconnection required. - Updating the actor state store component in place (for example a hot reload picking up a rotated secret referenced by the component) swaps the store instance without interrupting actor hosting: the store is resolved per call, so hosted actors and running workflows continue against the same backing store. Draining is reserved for the store being removed, unmarked, or replaced by a differently-named component.
Two further consistency fixes ride along with this change:
- Actor types are now only advertised to placement while an actor state store is configured. Previously an app declaring actor entities with no actor state store at all would still receive actor invocations, despite actor state, reminders, and workflows being non-functional, contradicting the documented requirement and the
actor hosting disabledlog. - Hot loading a second component marked as the actor state store is skipped with an error log naming the existing actor state store, instead of the duplicate silently corrupting the component store bookkeeping.
All sidecars in a namespace disconnected from Placement when a single sidecar disconnected mid-dissemination
Problem
Periodically, the Placement server closed every sidecar stream in a namespace within the same millisecond, and all sidecars reconnected at once. On reconnect each sidecar halts its local actors, so actors mid-call were interrupted on hosts that were perfectly healthy. The dissemination version restarted from scratch after each occurrence.
Impact
You were affected if you ran actor-hosting sidecars, most visibly in namespaces with many sidecars and frequent deployment rollouts. Each sidecar disconnecting while a dissemination round was in flight brought the namespace one step closer to a mass disconnect, so large, frequently rolled clusters could see every sidecar in a namespace dropped every few minutes. Actors mid-invocation on unaffected hosts were interrupted each time.
Root Cause
A single failing Placement stream could report its closure twice: once from its receive loop unwinding, and once from a failed dissemination send on the same connection. The per-namespace connection counter was decremented for both reports, leaving it one below the real number of live connections after each such event. Once the accumulated drift reached zero, the namespace’s dissemination loop was shut down while live streams were still attached, closing all of them and resetting the dissemination version.
Solution
A stream closure is now reported exactly once: a failed dissemination send cancels the stream’s context, preserving the send error as the close cause, and the receive loop unwinding is the single place a closure is reported from. The namespace connection counter now always matches the number of live streams, so a healthy namespace is never torn down by one sidecar’s disconnect.
Scheduler embedded etcd metrics missing from the metrics endpoint
Problem
After upgrading to 1.18, all embedded etcd metrics (etcd_*, such as etcd_server_has_leader, etcd_mvcc_db_total_size_in_bytes, and etcd_disk_backend_commit_duration_seconds_bucket) disappeared from the Scheduler’s metrics endpoint (default port 9090).
Dapr’s own dapr_scheduler_* metrics were unaffected.
Impact
You were affected if you scraped the Scheduler metrics endpoint for embedded etcd metrics, for example to populate the embedded etcd panels of the dapr-scheduler Grafana dashboard or to alert on etcd database size and disk commit latency.
Those series stopped being emitted after upgrading from 1.17 to 1.18.
Root Cause
In 1.18 the metrics exporter shared by all Dapr binaries switched from serving the process-global default Prometheus registry to a fresh private registry, to avoid duplicate collector registration panics when the exporter is restarted within the same process. Embedded etcd registers all of its collectors on the global default registry during package initialization, so its metrics were never written into the private registry the endpoint served, and were exposed nowhere.
Solution
The metrics endpoint now gathers from both the private registry and the global default registry.
Nothing is ever registered on the default registry by the exporter itself, so in-process restarts remain safe and the original panic cannot reoccur.
All etcd_* metrics are emitted on the Scheduler metrics endpoint again, and other metrics registered on the default registry by linked libraries (such as grpc_server_* and grpc_client_*) are restored to their 1.17 behavior across Dapr binaries.
Scheduler hanging on shutdown and failing to redeliver jobs after client disconnects
Problem
Two related failure modes in the Scheduler:
- A Scheduler instance could hang forever during shutdown, remaining as a zombie process: its gRPC transport stayed alive and its health endpoint kept returning 200, but no handler was serving. Connected daprd sidecars hung on in-flight calls to the zombie instead of failing over.
- After a daprd sidecar disconnected abruptly, the Scheduler could stop delivering jobs and actor reminders for that sidecar’s entire namespace, in some cases for minutes, even though other healthy sidecars remained connected.
Impact
A Scheduler restart or crash under load could escalate into a cluster-wide outage window: job and actor reminder delivery stalled until pods were manually restarted or clients reconnected and re-registered. You were affected if you run the Scheduler in HA with actively connected sidecars, most visibly under high job or workflow throughput.
Root Cause
Shutdown used an unbounded gRPC GracefulStop(), which waits for every open stream.
A WatchJobs stream whose client had connected but never sent, or had stopped reading, left its handler blocked in its initial Recv (or a flow-control blocked Send), so the drain never completed and the process never exited.
Readiness was only failed after the drain completed, so health probes never detected the zombie.
Separately, a dead WatchJobs stream emitted duplicate close events (one from its receive loop plus one per failed send), while the per-namespace connection counter was decremented once per event.
Duplicates could drive the count to zero while live streams remained, deleting the namespace and dropping every stream and deliverable job prefix in it.
Failed sends also held their job’s completion callback until the stream was fully reaped, so the scheduling engine could not promptly redeliver those jobs to healthy streams.
A data race in the connection event-loop object recycling could additionally corrupt loop state under connection churn.
Solution
Scheduler shutdown now fails readiness before draining and bounds the graceful drain to 5 seconds before forcing the gRPC server to stop, guaranteeing process exit. Dead streams are now closed exactly once: the close cancels the stream at detection time, promptly deregisters its deliverable prefixes, and resolves in-flight jobs as undeliverable so the engine immediately redelivers them to healthy streams. Namespace deletion is now confirmed by the connection tracking loop that owns the authoritative stream set, so duplicate or stray close events can no longer tear down a namespace that still has live streams. The event-loop object recycling race was removed.
Scheduler crash when scheduling a job with a malformed timezone-prefixed schedule
Problem
Scheduling a job whose schedule carried a timezone prefix (TZ= or CRON_TZ=) with no schedule after it, such as TZ=UTC, crashed the Scheduler process instead of returning an error.
Impact
Any client permitted to schedule a job could bring down a shared Scheduler with a single malformed schedule string. daprd does not validate the schedule; it forwards it verbatim to the Scheduler, which parses it, and the Scheduler’s gRPC server has no panic-recovery interceptor, so the parse panic terminated the process. In a highly available deployment this manifested as a Scheduler pod crash-looping whenever the offending job was (re)loaded.
Root Cause
The cron parser located the boundary between the timezone prefix and the schedule with strings.Index(spec, " ") and sliced the string on the result without checking for -1.
A prefix with no following schedule produced an index of -1, and the resulting out-of-range slice panicked.
Solution
The cron parser (from github.com/dapr/kit) now finds the boundary using any whitespace and returns a descriptive error when no schedule follows the timezone prefix.
The malformed schedule is now rejected when the job is scheduled, and the Scheduler no longer crashes.
Timezone prefix silently ignored on @every job schedules
Problem
A schedule that combined a timezone prefix with an @every interval, such as CRON_TZ=Europe/Rome @every 1h, was accepted but silently dropped the timezone.
Impact
The prefix looked like it pinned the job to a timezone, but an @every schedule fires at a fixed interval and has no wall-clock time for a timezone to apply to.
A user who set CRON_TZ=Europe/Rome @every 24h intending “every day at the same local time” would see the fire time drift by an hour across daylight saving transitions — the exact problem the timezone prefix appears to prevent.
Root Cause
An @every schedule parses to a fixed constant-delay schedule that carries no location, so the parsed timezone was discarded rather than applied.
Solution
The cron parser (from github.com/dapr/kit) now rejects a timezone prefix on an @every schedule when the job is scheduled, so the misconfiguration surfaces immediately instead of silently producing wrong fire times.
To pin a recurring job to a wall-clock time in a timezone, use a cron expression instead, for example CRON_TZ=Europe/Rome 0 0 9 * * *.
Workflow GetInstance intermittently failing while the workflow is running
Problem
A read-only workflow status query (GetInstance / GetWorkflowMetadata, or the equivalent SDK calls such as GetWorkflowStateAsync) could intermittently fail with a gRPC Unknown error while the queried workflow was running normally:
Status(StatusCode="Unknown", Detail="workflow '<id>': inbox key 'inbox-000000' declared in metadata (inboxLength=1) but missing from state store (transient store read failure or partial save?)")
The failure was transient and self-healing: the next poll for the same instance succeeded, and the workflow itself completed successfully.
Impact
You were affected if you polled workflow status while workflows were making progress, most visibly under high activity concurrency (for example a fan-out of many parallel activities polled by several concurrent clients), although a single activity transition could also trigger it. Callers received a terminal-looking error for a healthy workflow, so clients without their own retry logic surfaced spurious failures. The persisted workflow state was never actually inconsistent.
Root Cause
Loading workflow state reads the metadata row and the inbox-*/history-* entry rows in two separate state store calls, and the status query path performs these reads without holding the workflow actor’s lock.
A workflow actor save is a single atomic transaction that deletes consumed inbox entries and writes updated metadata, so a save committing between the reader’s two calls produced a torn read: the old metadata still declared inbox entries that the second read no longer found.
This torn read was reported as a hard error to the caller.
Solution
The workflow state load now detects this mismatch and retries the whole load with freshly read metadata, up to 5 attempts spaced 15 ms apart.
The metadata ETag is compared between attempts: a changed ETag proves a concurrent save landed between the reads (retry), while an unchanged ETag proves the entries are genuinely missing from the store, in which case the original error is still returned.
Status queries racing an active workflow now return consistent results instead of transient Unknown errors.
Input bindings not activated when the app is slow to answer the subscription discovery probe
Problem
Before it starts reading from an input binding, daprd asks the application whether it subscribes to that binding: an HTTP OPTIONS request to the binding’s route, or a gRPC ListInputBindings call.
This request was given a hardcoded 3 second budget with no way to change it.
An application that had not finished warming up within those 3 seconds never answered in time, so daprd treated the binding as unsubscribed and never activated it.
Impact
You were affected if your application is slow to serve its first request after startup — JVM or JIT warmup, large dependency-injection graphs, or resource-constrained nodes — and you declared an input binding without an explicit direction: input metadata entry.
The binding component itself initialized correctly and appeared in the sidecar’s metadata endpoint, so the component looked healthy while no events were ever delivered.
On the HTTP channel a failed probe also aborted the remaining bindings, leaving every input binding on that sidecar inactive; the only trace was a failed to read from bindings warning in the sidecar log.
On the gRPC channel the probe failure was silent.
Root Cause
The subscription discovery deadline was hardcoded to 3 seconds in the binding processor and built from a background context. It could neither be tuned for applications with slow startup nor cancelled when the runtime shut down while a probe was still in flight.
Solution
The timeout is now configurable through the new daprd --app-binding-options-timeout flag, which applies to both the HTTP OPTIONS probe and the gRPC ListInputBindings probe.
The default remains 3 seconds, so existing deployments are unchanged, and non-positive values fall back to that default.
The probe is now derived from the runtime’s context, so it is cancelled promptly on shutdown instead of running to its full deadline.
Setting direction: input on the binding component continues to skip the probe entirely.
MCPServer left unusable when its endpoint was briefly unavailable at startup
Problem
Loading an MCPServer resource connects to the MCP server and lists its tools, which is what installs that server’s dapr.internal.mcp.<name>.ListTools and .CallTool.<tool> workflows and registers the workflow actor host they run on.
If that connection failed, the failure was final: registration was never re-attempted for the life of the sidecar process.
Impact
You were affected if an MCPServer’s endpoint was not serving at the moment daprd loaded it, for example an origin still starting up, a proxy or tunnel in front of it returning 502 while its backend came up, or DNS that had not yet propagated.
The resource still appeared in the metadata API, so it looked loaded, but none of its workflows existed. A client that read the metadata and scheduled one of them found no host for the workflow actor type, and workflow creation retries a missing host indefinitely, so the call hung until the caller’s context expired instead of failing. With an unbounded context it did not return at all. Recovery required editing the resource to trigger a hot reload, or restarting the sidecar.
Root Cause
Registration was invoked exactly once per resource load with no retry, so a momentary connection failure was indistinguishable from a permanently unreachable endpoint. Both left the server listed in metadata and unusable.
Solution
MCPServer registration now runs under Dapr’s built-in initialization retry policy, the same one used elsewhere for resource initialization: exponential backoff starting at 500ms, up to 3 retries within a 10 second budget.
An endpoint that becomes reachable inside that window is registered normally and its tools work as expected.
A genuinely unreachable endpoint behaves as before once the retries are exhausted, and ignoreErrors: true continues to keep daprd running.
Crash when delivering a pub/sub message with a non-string trace field over HTTP
Problem
Delivering a pub/sub message to a subscriber over HTTP crashed the daprd process when the message’s CloudEvent carried a non-string traceparent or traceid field, for example a JSON number, boolean, or object instead of a string.
Impact
Any client permitted to publish to a subscribed topic could crash the sidecar with a single message.
A publisher controls the CloudEvent trace fields: publishing with content type application/cloudevents+json and a body such as {"specversion":"1.0", ..., "traceid":12345} preserves the non-string value all the way through to delivery.
The HTTP delivery path runs in a background goroutine with no panic recovery, so the failure terminated the whole process rather than dropping the single message.
The gRPC delivery path was not affected.
In a highly available deployment this manifested as sidecars crash-looping whenever the offending message was redelivered.
Root Cause
The HTTP pub/sub delivery path (Deliver and DeliverBulk in pkg/runtime/subscription/postman/http) read the trace field from the CloudEvent and performed an unchecked type assertion to string.
The CloudEvent is deserialized from publisher-controlled bytes into a map[string]any, so a non-string trace field became a float64, bool, or map, and the assertion panicked.
Solution
The HTTP delivery path now uses a checked type assertion, matching the gRPC delivery path: a non-string trace field is ignored (tracing is skipped for that message) instead of crashing the process. Messages carrying a malformed trace field are now delivered to the subscriber normally.
Workflow continue_as_new iterations sharing a single unbounded trace
Problem
When a workflow restarted itself with continue_as_new, the new iteration inherited the previous iteration’s trace context instead of starting a trace of its own.
Every iteration of an eternal workflow therefore joined the trace of the very first iteration, sharing one trace ID indefinitely.
Impact
You were affected if you ran eternal or polling workflows built on continue_as_new with distributed tracing enabled (OTLP or Zipkin exporter to any backend, such as Jaeger, Azure Monitor, or Geneva).
A workflow iterating over hours or days produced a single trace accumulating thousands of spans.
Workflows running without tracing enabled were unaffected.
Root Cause
On a continue_as_new completion, the durable task engine built the new iteration’s execution-started event by copying the previous iteration’s parent trace context verbatim.
Each iteration’s orchestration span was therefore parented off the original trace, and the trace grew without bound.
Solution
When the prior iteration was traced, each continue_as_new transition now generates a fresh W3C root trace context (new random trace and span IDs) for the new iteration, so every iteration produces its own bounded, independently queryable trace.
Activity spans and spans created inside the application continue to join their own iteration’s trace as before.
A workflow that was not being traced stays untraced across the transition.
Stalled workflows permanently stuck after the last workflow worker disconnects
Problem
A workflow that entered the STALLED state (version not available, patch mismatch, or payload size exceeded) could become permanently stuck if the last connected workflow worker disconnected while the workflow was stalled.
Reconnecting workers, including workers registering the exact workflow version the stall was waiting for, did not resume it: status queries kept reporting STALLED and the only recovery was restarting daprd.
When it occurred, the sidecar logged:
error while disconnecting work item stream: failed to deactivate workflow '<id>': actor is stalled
Impact
Stalling exists so that a workflow survives its workers going away and resumes once capable workers return, most commonly a rolling upgrade where the old application version disconnects and the new version reconnects.
You were affected if you use workflow versioning, patching, or a configured --max-body-size, and all workflow workers of an application disconnected while a workflow was stalled; for example during application restarts, rolling upgrades, scale-to-zero.
Root Cause
A stalled workflow actor parks its execution in-process, holding the execution reminder in flight until its context is cancelled, and marks its lock as stalled.
When the last workflow worker disconnects, daprd unregisters the workflow actor types and deactivates all workflow actors, but deactivation begins by acquiring the actor’s lock, and the lock rejects acquisition while the actor is stalled.
Whether the workflow could later recover came down to a race: if the scheduler’s reminder stream teardown cancelled the parked execution before deactivation reached the actor, deactivation succeeded and the unacknowledged reminder was redelivered when workers reconnected.
If deactivation won the race, it failed with actor is stalled, leaving the actor activated and holding the reminder in flight, so reconnecting workers had nothing to redeliver and the workflow never re-executed.
Solution
Deactivating a stalled workflow actor now wakes the parked execution instead of failing: the held execution returns immediately, leaving the execution reminder unacknowledged, and deactivation completes.
When workflow workers reconnect, the reminder is redelivered and the workflow re-executes, resuming and completing once the connected workers satisfy the stall condition (for example the required workflow version is registered, or daprd was restarted with a larger --max-body-size).
Recovery from a stall no longer depends on timing, and a daprd restart is no longer required.
Fixes orphaned workflow activity-result reminders retrying forever
Problem
When a workflow activity completes but its parent workflow actor is unreachable (for example during placement rebalancing or a host restart), the activity actor durably queues the result as an activity-result reminder on the workflow actor so the outcome is delivered once the workflow is reachable again.
If the workflow instance is purged before that reminder fires, the reminder becomes an orphan: it targets an instance that no longer exists.
Impact
The issue impacts users on Dapr 1.18.0-1.18.2 running workflows with activities where instances are purged (explicitly, or via a state retention policy) while activity results are still in flight, particularly across placement churn such as rolling restarts or scale events.
Each orphaned reminder adds a permanent one-invocation-per-second load on the daprd hosting the workflow actor type.
Root Cause
The workflow actor’s activity-result reminder handler forwarded the instance-not-found error to the reminder system without classifying it as terminal.
The scheduler treats any error as a failed invocation and applies the reminder’s failure policy, which for this reminder type is a constant one-second retry with no retry limit.
Solution
The activity-result reminder handler now treats instance-not-found as a successful delivery outcome:
At-least-once delivery for live instances is unchanged as the fix only affects reminders whose target instance has been purged.
Pub/sub delivery to a gRPC app failing with “use of closed network connection”
Problem
Delivering a pub/sub message to an application connected over gRPC intermittently failed before the application ever saw the message, and the sidecar logged:
error returned from app while processing pub/sub event <id>: retriable error occurred: rpc error: code = Unavailable desc = connection error: desc = "error reading server preface: read tcp 127.0.0.1:56264->127.0.0.1:14208: use of closed network connection"
The application was healthy and listening throughout, and the same message was delivered successfully on a later attempt or after a sidecar restart.
Impact
You were affected if you ran an application with --app-protocol grpc (or grpcs) subscribing to topics, and had upgraded to 1.18.
The failures were most visible on subscriptions receiving sporadic traffic, and on hosts where the application is slow to accept a new connection, for example a busy thread pool or a garbage collection pause.
Every affected delivery was reported to the broker as a retriable failure, so the message was either redelivered by the broker (RabbitMQ, Kafka, and other components with redelivery) or dropped for components without it.
The same connection handling is used for service invocation into a gRPC application, input binding delivery, and job triggers, so those paths could fail the same way.
Applications using --app-protocol http were not affected.
Root Cause
daprd dialed the application with a MinConnectTimeout of one second.
In gRPC that value is the budget for an entire connection attempt, covering the TCP connect and the HTTP/2 handshake, not just the TCP connect: when it expires, gRPC hard-closes the socket, and a request already riding on that connection fails with error reading server preface: use of closed network connection.
Up to 1.17 this was almost never reachable, because daprd held a single application connection for the lifetime of the process and only ever dialed at startup. In 1.18 the application connection moved into the same pool used for sidecar-to-sidecar connections, so connections are established on demand while messages are being delivered, and each new connection had one second to complete its handshake before the delivery riding on it failed.
The pool made this far more frequent than it needed to be. The pool is configured to keep one warm connection to the application, but once that connection had been idle for longer than the pool’s three minute idle window it was neither handed out again (it was treated as expired) nor closed (it was the connection being kept warm). It was stuck in the pool, unusable and open, so every request arriving after an idle period dialed a brand new connection rather than reusing the warm one.
Solution
A connection attempt to the application is now given gRPC’s default budget of 20 seconds instead of one second, matching what daprd already used for sidecar-to-sidecar connections. This does not change how long a request waits for an unreachable application: a refused connection still fails immediately, and how long a request waits remains governed by the caller’s context and resiliency policy.
Connections the pool holds to satisfy its warm connection minimum are also no longer expired out. The application connection established at startup now stays in use for the life of the sidecar, so a subscription that receives one message an hour no longer re-dials the application for every message.
Workflow terminate silently dropped when delivered in the same batch as other events
Problem
Terminating a workflow intermittently had no effect: the workflow’s status stayed RUNNING and it kept executing activities and timers as if the terminate had never been issued.
Blocking terminate calls (such as TerminateWorkflowAsync in the .NET SDK) never returned, because they wait for the instance to reach a terminal status that never came.
With debug logging enabled, a dropped terminate showed the ExecutionTerminated event being delivered and consumed with no effect:
received work item with 2 new event(s): [ExecutionTerminated, TaskCompleted#1]
workflow execution returned with status 'ORCHESTRATION_STATUS_RUNNING'
A workflow terminated while it was suspended was affected the same way, deterministically: the status stayed SUSPENDED and every terminate sent to it was lost.
Impact
You were affected if you terminated workflows that were actively making progress, regardless of SDK language.
The window depends on inbox pressure: the terminate is dropped when it arrives in the same work item batch as another event and is not the last event in that batch, so long-running workflows that loop over short activities and timers (endless pollers, monitors, continue_as_new loops) were the most exposed, while a workflow idling on a single slow activity or timer almost always received its terminate alone and was unaffected.
A dropped terminate is consumed with its batch and never redelivered, so the instance kept running; a retried terminate raced the same window again. A recursive terminate that was dropped also never cascaded, leaving child workflows running as orphans. For suspended workflows there was no window: every terminate was lost until the workflow was resumed.
Root Cause
The workflow engine hands an instance’s pending events to the workflow executor as one batch, and relied entirely on the SDK executor to turn an ExecutionTerminated event into a termination outcome.
SDK executors registered the termination when they processed the event but kept feeding the remaining events of the batch into the workflow code, which resumed past its own termination and produced a competing outcome: scheduling more work, completing normally, or restarting via continue_as_new.
A continue_as_new outcome always discarded the termination; other outcomes raced it nondeterministically.
A terminate delivered while the workflow was suspended produced no outcome at all, because suspension suppressed every action the executor would have returned.
Since the engine trusted the executor’s result and the event was consumed with the batch, the terminate was lost permanently.
Solution
When a delivered batch contains an ExecutionTerminated event and the executor does not complete the workflow, daprd discards the doomed execution’s pending work and completes the instance as TERMINATED.
A continue_as_new returned alongside a terminate no longer starts a new iteration, and terminating a suspended workflow now terminates it without requiring a resume.
Azure component authentication halting at the SPIFFE credential instead of falling back
Problem
Azure (Microsoft Entra ID) components authenticate by trying a chain of credentials in order until one succeeds.
When azureClientId and azureTenantId were set, the chain included the SPIFFE workload identity credential, and if no SPIFFE JWT SVID source was available the chain stopped at that step:
ChainedTokenCredential: failed to acquire a token.
Attempted credentials:
ClientAssertionCredential: failed to get JWT SVID source from context
Credentials later in the chain, such as managed identity or the Azure CLI, were never attempted, so the component failed to authenticate even though a working credential was available.
Impact
You were affected on Dapr 1.16.0 or later (where the SPIFFE credential joined the default chain) in either of these configurations:
- An Azure component with
azureClientIdandazureTenantIdset but no client secret or certificate, relying on a later credential in the default chain (for example managed identity or the Azure CLI). - An explicit
azureAuthMethodslist placingspiffeworkloadidentitybefore another method (for examplespiffeworkloadidentity,managedidentity), expecting fallback when SPIFFE is not configured.
Root Cause
ChainedTokenCredential only continues past a credential that reports a credentialUnavailableError; any other error is treated as fatal and ends the chain.
The SPIFFE credential returned a plain error when the context carried no JWT SVID source, so a missing prerequisite was treated as a fatal authentication failure rather than a signal to try the next credential.
Solution
The SPIFFE credential now reports itself as unavailable when no JWT SVID source is present, before any token request is made, which is exactly what ChainedTokenCredential requires to continue to the next credential in the chain.
Both the default chain and explicitly ordered azureAuthMethods lists now fall through as expected, and behavior when a SPIFFE source is configured is unchanged.
下载链接
- daprd_darwin_amd64.tar.gz
- daprd_darwin_amd64.tar.gz.sha256
- daprd_darwin_arm64.tar.gz
- daprd_darwin_arm64.tar.gz.sha256
- daprd_linux_amd64-stablecomponents.tar.gz
- daprd_linux_amd64-stablecomponents.tar.gz.sha256
- daprd_linux_amd64.tar.gz
- daprd_linux_amd64.tar.gz.sha256
- daprd_linux_arm-stablecomponents.tar.gz
- daprd_linux_arm-stablecomponents.tar.gz.sha256
- daprd_linux_arm.tar.gz
- daprd_linux_arm.tar.gz.sha256
- daprd_linux_arm64-stablecomponents.tar.gz
- daprd_linux_arm64-stablecomponents.tar.gz.sha256
- daprd_linux_arm64.tar.gz
- daprd_linux_arm64.tar.gz.sha256
- daprd_windows_amd64.zip
- daprd_windows_amd64.zip.sha256
- grafana-actor-dashboard.json
- grafana-actor-dashboard.json.sha256
- grafana-sidecar-dashboard.json
- grafana-sidecar-dashboard.json.sha256
- grafana-system-services-dashboard.json
- grafana-system-services-dashboard.json.sha256
- injector_darwin_amd64.tar.gz
- injector_darwin_amd64.tar.gz.sha256
- injector_darwin_arm64.tar.gz
- injector_darwin_arm64.tar.gz.sha256
- injector_linux_amd64.tar.gz
- injector_linux_amd64.tar.gz.sha256
- injector_linux_arm.tar.gz
- injector_linux_arm.tar.gz.sha256
- injector_linux_arm64.tar.gz
- injector_linux_arm64.tar.gz.sha256
- injector_windows_amd64.zip
- injector_windows_amd64.zip.sha256
- operator_darwin_amd64.tar.gz
- operator_darwin_amd64.tar.gz.sha256
- operator_darwin_arm64.tar.gz
- operator_darwin_arm64.tar.gz.sha256
- operator_linux_amd64.tar.gz
- operator_linux_amd64.tar.gz.sha256
- operator_linux_arm.tar.gz
- operator_linux_arm.tar.gz.sha256
- operator_linux_arm64.tar.gz
- operator_linux_arm64.tar.gz.sha256
- operator_windows_amd64.zip
- operator_windows_amd64.zip.sha256
- placement_darwin_amd64.tar.gz
- placement_darwin_amd64.tar.gz.sha256
- placement_darwin_arm64.tar.gz
- placement_darwin_arm64.tar.gz.sha256
- placement_linux_amd64.tar.gz
- placement_linux_amd64.tar.gz.sha256
- placement_linux_arm.tar.gz
- placement_linux_arm.tar.gz.sha256
- placement_linux_arm64.tar.gz
- placement_linux_arm64.tar.gz.sha256
- placement_windows_amd64.zip
- placement_windows_amd64.zip.sha256
- scheduler_darwin_amd64.tar.gz
- scheduler_darwin_amd64.tar.gz.sha256
- scheduler_darwin_arm64.tar.gz
- scheduler_darwin_arm64.tar.gz.sha256
- scheduler_linux_amd64.tar.gz
- scheduler_linux_amd64.tar.gz.sha256
- scheduler_linux_arm.tar.gz
- scheduler_linux_arm.tar.gz.sha256
- scheduler_linux_arm64.tar.gz
- scheduler_linux_arm64.tar.gz.sha256
- scheduler_windows_amd64.zip
- scheduler_windows_amd64.zip.sha256
- sentry_darwin_amd64.tar.gz
- sentry_darwin_amd64.tar.gz.sha256
- sentry_darwin_arm64.tar.gz
- sentry_darwin_arm64.tar.gz.sha256
- sentry_linux_amd64.tar.gz
- sentry_linux_amd64.tar.gz.sha256
- sentry_linux_arm.tar.gz
- sentry_linux_arm.tar.gz.sha256
- sentry_linux_arm64.tar.gz
- sentry_linux_arm64.tar.gz.sha256
- sentry_windows_amd64.zip
- sentry_windows_amd64.zip.sha256