Skip to content

fix: 0-segment retry loop, event bus isolation, and dashboard snapshot serve - #117

Closed
sheepweb wants to merge 9 commits into
RikaCelery:mainfrom
sheepweb:pr/0seg-retry-fixes
Closed

fix: 0-segment retry loop, event bus isolation, and dashboard snapshot serve#117
sheepweb wants to merge 9 commits into
RikaCelery:mainfrom
sheepweb:pr/0seg-retry-fixes

Conversation

@sheepweb

Copy link
Copy Markdown

Summary

Clean PR from the sheepweb/XhRec fork. Cherry-picked 7 business commits from origin/main, excluding CCG tooling commits.

Commits

# SHA Description
1 a9bc9be prevent zombie re-arm loop for rooms producing 0 segments
2 b86b8b6 replace never-retry with exponential backoff for 0-segment failures
3 526f725 use fixed 30s retry interval instead of exponential backoff
4 2e04b21 indefinite 30s retry, stop when room deactivated
5 eb5027f keep 30s retries until room becomes unrecordable
6 ec81671 keep request bus commands off lossy event stream
7 c7ceaf1 serve dashboard from component snapshots

Changes

9 source files, +118 / −16 — all under src/:

  • SchedulerComponent.kt — 0-segment retry lifecycle (5 iterative fixes)
  • SessionComponent.kt — zombie loop guard
  • Events.kt — event type refinements
  • EventBus.kt + EventBusTest.kt — request bus command isolation from lossy event stream
  • Main.kt, HttpServerComponent.kt, MetricComponent.kt, RoomComponent.kt — dashboard snapshot serving

Verification

  • Branch from upstream/main (3a30f4a) — 7 commits cherry-picked cleanly
  • CCG commits (0b302a2, 60a4b5b) and .ccg/ directory excluded (zero .ccg paths in diff)
  • PR check: runs ./gradlew test — EventBusTest included in this change set

sheepweb and others added 9 commits June 21, 2026 20:29
RecordingStopped now carries segmentsDispatched count from
pollingLoop. SchedulerComponent only re-arms when >0 segments
were actually dispatched. Rooms that return 404 before any
download (index=-1 / 0 segments) wait for RoomStatusChanged
instead of entering an infinite start-stop cycle.

Also fixes silent session discard: startSession no longer
silently returns when stuck in Fetching state — it cancels
the stale pollingJob and allows re-entry. configureSession
failure now publishes RecordingStopped(0) to properly
notify the scheduler.
0-segment RecordingStopped now retries with backoff (30s/60s/120s/240s/480s)
instead of permanently giving up. Max 5 attempts, then waits for
RoomStatusChanged. This prevents rooms that are genuinely public
but experience transient CDN unavailability from being stuck
forever, while still avoiding the infinite zombie loop.
…tivated

Removed max retry limit and exponential backoff. 0-segment
RecordingStopped now retries every 30s indefinitely. The
loop terminates naturally when the room is deactivated or
the system shuts down - no artificial cap needed.
Track rooms currently in a recordable state (public, or groupShow with autopay).
0-segment retries now continue every 30s while the room remains recordable and
stop automatically once RoomStatusChanged reports an unrecordable state or the
room is deactivated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RikaCelery

This comment was marked as outdated.

@RikaCelery
RikaCelery self-requested a review June 28, 2026 02:34

@RikaCelery RikaCelery left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

有些问题,感觉没有找到核心原因,再考虑一下

val client = ClientManager.getProxiedClient("api")
val response = withRetry(3) {
client.get("https://stripchat.com/api/front/v3/config/initial") {
client.get("https://poplive.xyz/api/front/v3/config/initial") {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

自己验证一下API端点通不通,需不需要额外验证

try {
val client = ClientManager.getProxiedClient("event")
client.webSocket("wss://websocket-v6.xhamsterlive.com/connection/websocket") {
client.webSocket("wss://websocket-v6.poplive.xyz/connection/websocket") {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

连不上,再确认一下poplive和xhamsterlive到底是不是一样,尤其是直播间内消息和流地址

}

fun snapshotRooms(): List<Room> = rooms.values.map { it.copy() }

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这种不要单独开一个函数然后注入HttpComponent,eventBus的存在就是尽可能降低组件之间来回注入的。
如果觉得会导致Bus卡住应该在命令收发侧去调试, 或者设计全异步的命令处理流程

) : Actor<SchedulerMsg>("SchedulerComponent", eventBus, parentScope) {

private val armed = ConcurrentHashMap<Long, ArmedRoom>()
private val recordableRooms = ConcurrentHashMap.newKeySet<Long>()

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

加这个的作用是什么?

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

看了一圈这个变量除了记录当前状态还有别的做用吗。

Scheduler不需要缓存当前房间是否可以录制,只需要在每次尝试启动Session之前判断是否可以录制即可

val client = ClientManager.getProxiedClient("api")
val response = withRetry(3, stopIf = { false }) {
client.post("https://stripchat.com/api/front/show/models/$roomId/groupShows/${user.userId}") {
client.post("https://poplive.xyz/api/front/show/models/$roomId/groupShows/${user.userId}") {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

同上

return sb.toString()
}

fun snapshotRoomDetailedStatus(): Map<Long, Map<String, Any>> =

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

再考虑一下,不要绕开EventBus,这点消息量完全处理的过来

}
} else {
logger.debug("Recording stopped for room {}", event.roomId)
logger.info(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里当时设计的是让Scheduler启动一次录制会话(Session),只要Scheduler没说停止录制Session就应该一直尝试去录制。

也就是说,Session要自己维护一个状态机,处理好启动失败时候的重试。如果session决定不再重试了,要告诉Scheduler,不然Scheduler会认为Session依然在录制。

if (existing != null) {
val blocked = when (existing.state) {
SessionState.Fetching, SessionState.Recording -> true
SessionState.Fetching -> {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这算一个防止重复启动的逻辑,并不确定上层是否会同时启动两次。

这里只需要确保一次startSession中,state一定会从Fetching迁移到Recording或者Closing即可,不需要在第二次调用的时候停止第一次的


try {
const res = await fetch(`https://zh.xhamsterlive.com/api/front/v1/broadcasts/${modelName}`);
const res = await fetch(`https://poplive.xyz/api/front/v1/broadcasts/${modelName}`);

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

跟上面的一样,再确认一下

Comment thread README_zh-CN.md
# https://stripchat.com/modelA q:720p limit:120
; https://stripchat.com/modelB q:240p
https://stripchat.com/modelC q:highest
# https://poplive.xyz/modelA q:720p limit:120

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

如果改了这里,注意Bootstrap组件加载房间的时候会不会有问题

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

但本来项目就是为了xhamster写的,我觉得不改README比较好

@sheepweb sheepweb closed this Jul 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants