-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
refactor: Replace SSE with WebSocket #36965
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
Open
mohammad-rj
wants to merge
98
commits into
go-gitea:main
Choose a base branch
from
mohammad-rj:feat/websocket-migration
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,765
−1,077
Open
Changes from 5 commits
Commits
Show all changes
98 commits
Select commit
Hold shift + click to select a range
cb33214
feat(websocket): Phase 1 — replace SSE notification count with WebSocket
mohammad-rj 6073438
chore: add github.com/coder/websocket dependency to go.mod
mohammad-rj 3076f90
fix(websocket): use gitea modules/json, write raw bytes directly
mohammad-rj 096bdd0
fix(websocket): avoid data race with timeutil.MockUnset in tests
mohammad-rj 6aba649
chore: run make tidy to add coder/websocket to go-licenses.json
mohammad-rj 89e5084
fix(websocket): address silverwind review feedback
mohammad-rj 1537d8f
fix(websocket): auth via IsSigned check instead of reqSignIn middleware
mohammad-rj f2450cc
fix(websocket): remove export{} from sharedworker entry point, rename…
mohammad-rj c1ba182
fix(websocket): declare sharedworker as ES module and fix port cleanup
mohammad-rj b9a94c6
feat(websocket): Phase 1 — replace SSE notification count with WebSocket
mohammad-rj 69324f3
chore: add github.com/coder/websocket dependency to go.mod
mohammad-rj f26aa2f
fix(websocket): use gitea modules/json, write raw bytes directly
mohammad-rj 634b638
fix(websocket): avoid data race with timeutil.MockUnset in tests
mohammad-rj d526aa6
chore: run make tidy to add coder/websocket to go-licenses.json
mohammad-rj 1a576b1
fix(websocket): address silverwind review feedback
mohammad-rj b47686c
fix(websocket): auth via IsSigned check instead of reqSignIn middleware
mohammad-rj bf564d3
fix(websocket): remove export{} from sharedworker entry point, rename…
mohammad-rj 7e8f5a8
fix(websocket): declare sharedworker as ES module and fix port cleanup
mohammad-rj 2a902d1
fix(websocket): add WsSource to eventsource.sharedworker, remove webs…
mohammad-rj 48ec6f2
ci: add compliance workflow
mohammad-rj ed3bd91
ci: expand compliance workflow to match GitHub pull-compliance.yml (1…
mohammad-rj af2fadb
ci: add cleanup steps after each job to free disk space
mohammad-rj 3ec0ed9
ci: trigger run with capacity=5 parallel workers
mohammad-rj 155ef8f
ci: trigger run after disk cleanup
mohammad-rj 42f5342
Merge upstream/main to bring branch up to date with Vite migration an…
mohammad-rj 36b28c7
fix(websocket): add WsSource to eventsource.sharedworker, remove webs…
mohammad-rj 256aeb9
feat(websocket): Phase 2 — migrate stopwatches/logout to WebSocket, r…
mohammad-rj bf7c30e
fix(websocket): replace json.RawMessage with any in stopwatchesEvent
mohammad-rj 9cf7ea8
fix(stopwatch): show icon dynamically in tabs loaded before stopwatch…
mohammad-rj 0dc3607
fix(stopwatch): publish empty-stopwatches push on stop, not only cancel
mohammad-rj 075af8e
fix(stopwatch): prevent page reload when stopping/cancelling from nav…
mohammad-rj 111ca0c
fix(stopwatch): prevent page reload when starting/stopping from issue…
mohammad-rj 10f8e1e
chore(stopwatch): remove dead no-event-source code paths
mohammad-rj 99ad25b
chore: remove local CI workflow (not for upstream)
mohammad-rj fdc94c3
Merge remote-tracking branch 'origin/feat/websocket-migration' into c…
mohammad-rj 0af095a
Merge remote-tracking branch 'upstream/main' into ci-phase2-push
mohammad-rj cb41638
refactor(websocket): unify Init, add notification hook, accept anonym…
mohammad-rj abe5c17
refactor(websocket): extract notification and stopwatch publish helpers
mohammad-rj f54d567
fix(stopwatch): register sidebar handlers before navbar early return
mohammad-rj 98ad58f
Merge remote-tracking branch 'upstream/main' into final-for-github
mohammad-rj ec54754
Merge remote-tracking branch 'origin/main' into feat/websocket-migration
silverwind 0626009
websocket: add server-side ping keepalive and pubsub broker tests
silverwind 7004250
refactor: clean up residual EventSource/SSE references
silverwind 5e6e385
websocket: address open review feedback
silverwind 0627122
websocket: address remaining review follow-ups
silverwind f6e894a
websocket: tighten event wire contract
silverwind a308c74
websocket: narrow scope to the SSE→WebSocket migration
silverwind ce24ef7
websocket: tear down cleanly on shutdown, drop empty broker topics
silverwind e483e4c
websocket: make the pipeline push-only, drop all polling
silverwind 696e792
Merge remote-tracking branch 'origin/main' into pr-36965-ci
silverwind eb8d79b
fix(stopwatch): only set form-fetch-action class when a stopwatch is …
silverwind 509ce95
websocket: fold publish helper, share TS event-name constants
silverwind 4cd8010
websocket: correct the sharedworker duplicate-constants comment
silverwind c730de1
websocket: share event-name constants via module SharedWorker
silverwind eb3be05
websocket: fix ping deadlock and worker reconnect leak
silverwind e83f616
websocket: clean up comments, simplify event path, add e2e coverage
silverwind 31a5342
websocket: drop ping-cycle e2e test and its env override
silverwind 159df9e
Replace USER_EVENT_* string constants with TypeScript union types
silverwind 51bdf3f
manifest_test: fix wrong path for user-events.sharedworker entry
silverwind 25a2b0f
e2e: drop __userEventsWsReady, use retry/timeout for connect race
silverwind 00957e0
move user-events types into shared types.ts
silverwind c2a2778
simplify type sync comment
silverwind 84fe4ae
silence playwright/no-wait-for-timeout in events tests
silverwind c6f5df2
share single SharedWorker between notification and stopwatch
silverwind 6e2e733
refactor user-events worker into typed pub/sub singleton
silverwind f6c83fd
simplify: drop dead status path, trim narrating comments
silverwind 295177e
Merge branch 'main' into feat/websocket-migration
silverwind 37d6a7c
type user-events as a discriminated union, drop JSON round-trip
silverwind 368a080
ws: jitter reconnect, stop on auth-required close
silverwind 713b885
ws: use IANA close code 3000 (Unauthorized) instead of custom 4401
silverwind 3bc2c9d
Merge branch 'main' into feat/websocket-migration
wxiaoguang 2133553
Update routers/web/websocket/websocket.go
wxiaoguang 88740eb
tests/e2e: cover sidebar Start timer button click path
silverwind 022a805
fix(stopwatch): JSONOK instead of JSONRedirect("") for sidebar actions
silverwind aa0aff1
websocket: drop unused routing import
silverwind 23e1406
Merge remote-tracking branch 'origin/main' into feat/websocket-migration
silverwind 6284286
tests: update TestRequireSignInView to use /-/ws
silverwind 102db5e
ws: write timeout + clarify close code 3000 comment
silverwind 76bfed9
drop unused noscript href on active-stopwatch icon
silverwind 8bab18a
stopwatch: revert JSONOK back to JSONRedirect("")
silverwind 56ecb7d
Merge remote-tracking branch 'origin/main' into feat/websocket-migration
silverwind 864370c
ws: drop redundant logoutPrefix byte-prefix scan
silverwind b162b44
pubsub: make Subscribe cancel idempotent
silverwind cee3dc4
minor cleanups
silverwind c648306
tests/e2e: parallelize loginUser with API setup in stopwatch tests
silverwind 08493cf
refactor(notifications): lift NotificationCountChange into service wr…
silverwind e7ecf16
refactor(pubsub): extract Broker interface, rename impl to MemoryBroker
silverwind f489ad7
feat(pubsub): add Redis backend for cross-process event fan-out
silverwind f927198
simplify: pubsub redis concurrency, wrapper API consistency
silverwind 2a49b99
test(pubsub): swap real redis-server for miniredis, add cross-broker …
silverwind 508b180
review fixes: redis subscribe lock window, ping context, worker unsub…
silverwind 6826c42
Merge branch 'main' into feat/websocket-migration
silverwind f0a8bb0
fix(pubsub): wait for SUBSCRIBE ack before returning from Subscribe
silverwind 7f36592
simplify pass: pubsub retry, dispatch dedup, rewriteLogout fast path
silverwind a910a1d
Merge branch 'main' into feat/websocket-migration
lunny 89dd1b8
Merge remote-tracking branch 'upstream/main' into feat/websocket-migr…
8b0dee2
fix: remove leftover SSE-era imports and template literal after mergi…
47671b8
refactor(websocket): scope pubsub settings under [websocket] section
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // Copyright 2024 The Gitea Authors. All rights reserved. | ||
|
mohammad-rj marked this conversation as resolved.
Outdated
|
||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package websocket | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "code.gitea.io/gitea/modules/log" | ||
| "code.gitea.io/gitea/services/context" | ||
| "code.gitea.io/gitea/services/pubsub" | ||
|
|
||
| gitea_ws "github.com/coder/websocket" | ||
| ) | ||
|
|
||
| // Serve handles WebSocket upgrade and event delivery for the signed-in user. | ||
| func Serve(ctx *context.Context) { | ||
| if !ctx.IsSigned { | ||
|
mohammad-rj marked this conversation as resolved.
Outdated
|
||
| ctx.Status(401) | ||
| return | ||
| } | ||
|
|
||
| conn, err := gitea_ws.Accept(ctx.Resp, ctx.Req, &gitea_ws.AcceptOptions{ | ||
| InsecureSkipVerify: false, | ||
| }) | ||
| if err != nil { | ||
| log.Error("websocket: accept failed: %v", err) | ||
| return | ||
| } | ||
| defer conn.CloseNow() //nolint:errcheck // CloseNow is best-effort; error is intentionally ignored | ||
|
|
||
| topic := fmt.Sprintf("user-%d", ctx.Doer.ID) | ||
| ch, cancel := pubsub.DefaultBroker.Subscribe(topic) | ||
| defer cancel() | ||
|
mohammad-rj marked this conversation as resolved.
Outdated
|
||
|
|
||
| wsCtx := ctx.Req.Context() | ||
| for { | ||
| select { | ||
| case <-wsCtx.Done(): | ||
| return | ||
| case msg, ok := <-ch: | ||
| if !ok { | ||
| return | ||
| } | ||
| if err := conn.Write(wsCtx, gitea_ws.MessageText, msg); err != nil { | ||
| log.Trace("websocket: write failed: %v", err) | ||
| return | ||
| } | ||
|
silverwind marked this conversation as resolved.
|
||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| // Copyright 2024 The Gitea Authors. All rights reserved. | ||
|
mohammad-rj marked this conversation as resolved.
Outdated
|
||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package pubsub | ||
|
|
||
| import ( | ||
| "sync" | ||
| ) | ||
|
|
||
| // Broker is a simple in-memory pub/sub broker. | ||
| // It supports fan-out: one Publish call delivers the message to all active subscribers. | ||
| type Broker struct { | ||
| mu sync.RWMutex | ||
| subs map[string][]chan []byte | ||
| } | ||
|
|
||
| // DefaultBroker is the global singleton used by both routers and notifiers. | ||
| var DefaultBroker = NewBroker() | ||
|
|
||
| // NewBroker creates a new in-memory Broker. | ||
| func NewBroker() *Broker { | ||
| return &Broker{ | ||
| subs: make(map[string][]chan []byte), | ||
| } | ||
| } | ||
|
|
||
| // Subscribe returns a channel that receives messages published to topic. | ||
| // Call the returned cancel function to unsubscribe. | ||
| func (b *Broker) Subscribe(topic string) (<-chan []byte, func()) { | ||
| ch := make(chan []byte, 8) | ||
|
|
||
| b.mu.Lock() | ||
| b.subs[topic] = append(b.subs[topic], ch) | ||
| b.mu.Unlock() | ||
|
|
||
| cancel := func() { | ||
| b.mu.Lock() | ||
| defer b.mu.Unlock() | ||
| subs := b.subs[topic] | ||
| for i, sub := range subs { | ||
| if sub == ch { | ||
| b.subs[topic] = append(subs[:i], subs[i+1:]...) | ||
| break | ||
| } | ||
| } | ||
| close(ch) | ||
| } | ||
| return ch, cancel | ||
| } | ||
|
|
||
| // Publish sends msg to all subscribers of topic. | ||
| // Non-blocking: slow subscribers are skipped. | ||
| func (b *Broker) Publish(topic string, msg []byte) { | ||
| b.mu.RLock() | ||
| subs := b.subs[topic] | ||
| b.mu.RUnlock() | ||
|
|
||
| for _, ch := range subs { | ||
| select { | ||
| case ch <- msg: | ||
| default: | ||
| // subscriber too slow — skip | ||
| } | ||
| } | ||
|
mohammad-rj marked this conversation as resolved.
Outdated
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| // Copyright 2024 The Gitea Authors. All rights reserved. | ||
|
mohammad-rj marked this conversation as resolved.
Outdated
|
||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package websocket | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "time" | ||
|
|
||
| activities_model "code.gitea.io/gitea/models/activities" | ||
| "code.gitea.io/gitea/modules/graceful" | ||
| "code.gitea.io/gitea/modules/json" | ||
| "code.gitea.io/gitea/modules/log" | ||
| "code.gitea.io/gitea/modules/process" | ||
| "code.gitea.io/gitea/modules/setting" | ||
| "code.gitea.io/gitea/modules/timeutil" | ||
| "code.gitea.io/gitea/services/pubsub" | ||
| ) | ||
|
|
||
| // nowTS returns the current time as a TimeStamp using the real wall clock, | ||
| // avoiding data races with timeutil.MockUnset during tests. | ||
| func nowTS() timeutil.TimeStamp { | ||
| return timeutil.TimeStamp(time.Now().Unix()) | ||
| } | ||
|
|
||
| type notificationCountEvent struct { | ||
| Type string `json:"type"` | ||
| Count int64 `json:"count"` | ||
| } | ||
|
|
||
| func userTopic(userID int64) string { | ||
| return fmt.Sprintf("user-%d", userID) | ||
| } | ||
|
|
||
| // Init starts the background goroutine that polls notification counts | ||
| // and pushes updates to connected WebSocket clients. | ||
| func Init() error { | ||
| go graceful.GetManager().RunWithShutdownContext(run) | ||
| return nil | ||
| } | ||
|
silverwind marked this conversation as resolved.
|
||
|
|
||
| func run(ctx context.Context) { | ||
| ctx, _, finished := process.GetManager().AddTypedContext(ctx, "Service: WebSocket", process.SystemProcessType, true) | ||
| defer finished() | ||
|
|
||
| if setting.UI.Notification.EventSourceUpdateTime <= 0 { | ||
| return | ||
| } | ||
|
|
||
| then := nowTS().Add(-2) | ||
| timer := time.NewTicker(setting.UI.Notification.EventSourceUpdateTime) | ||
| defer timer.Stop() | ||
|
mohammad-rj marked this conversation as resolved.
Outdated
|
||
|
|
||
| for { | ||
| select { | ||
| case <-ctx.Done(): | ||
| return | ||
| case <-timer.C: | ||
| now := nowTS().Add(-2) | ||
|
|
||
| uidCounts, err := activities_model.GetUIDsAndNotificationCounts(ctx, then, now) | ||
|
mohammad-rj marked this conversation as resolved.
Outdated
|
||
| if err != nil { | ||
| log.Error("websocket: GetUIDsAndNotificationCounts: %v", err) | ||
| continue | ||
| } | ||
|
|
||
| for _, uidCount := range uidCounts { | ||
| msg, err := json.Marshal(notificationCountEvent{ | ||
| Type: "notification-count", | ||
| Count: uidCount.Count, | ||
| }) | ||
| if err != nil { | ||
| continue | ||
| } | ||
| pubsub.DefaultBroker.Publish(userTopic(uidCount.UserID), msg) | ||
| } | ||
|
|
||
| then = now | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.