fix: OnStart ctx bug, rename session→user, full logout cleanup
All checks were successful
check / check (push) Successful in 1m57s
All checks were successful
check / check (push) Successful in 1m57s
- Use context.Background() for cleanup goroutine instead of OnStart ctx which is cancelled after startup completes - Rename GetSessionCount→GetUserCount, DeleteStaleSessions→ DeleteStaleUsers to reflect that sessions represent users - HandleLogout now fully cleans up when last client disconnects: parts all channels (notifying members via QUIT), removes empty channels, and deletes the session/user record - docker build passes, all tests green, 0 lint issues
This commit is contained in:
@@ -796,8 +796,8 @@ func (database *Database) DeleteClient(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSessionCount returns the number of active sessions.
|
// GetUserCount returns the number of active users.
|
||||||
func (database *Database) GetSessionCount(
|
func (database *Database) GetUserCount(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
) (int64, error) {
|
) (int64, error) {
|
||||||
var count int64
|
var count int64
|
||||||
@@ -808,7 +808,7 @@ func (database *Database) GetSessionCount(
|
|||||||
).Scan(&count)
|
).Scan(&count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf(
|
return 0, fmt.Errorf(
|
||||||
"get session count: %w", err,
|
"get user count: %w", err,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -838,9 +838,9 @@ func (database *Database) ClientCountForSession(
|
|||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteStaleSessions removes clients not seen since the
|
// DeleteStaleUsers removes clients not seen since the
|
||||||
// cutoff and cleans up orphaned sessions.
|
// cutoff and cleans up orphaned users (sessions).
|
||||||
func (database *Database) DeleteStaleSessions(
|
func (database *Database) DeleteStaleUsers(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cutoff time.Time,
|
cutoff time.Time,
|
||||||
) (int64, error) {
|
) (int64, error) {
|
||||||
|
|||||||
@@ -1361,20 +1361,23 @@ func (hdlr *Handlers) canAccessChannelHistory(
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleLogout deletes the authenticated client's token.
|
// HandleLogout deletes the authenticated client's token
|
||||||
|
// and cleans up the user (session) if no clients remain.
|
||||||
func (hdlr *Handlers) HandleLogout() http.HandlerFunc {
|
func (hdlr *Handlers) HandleLogout() http.HandlerFunc {
|
||||||
return func(
|
return func(
|
||||||
writer http.ResponseWriter,
|
writer http.ResponseWriter,
|
||||||
request *http.Request,
|
request *http.Request,
|
||||||
) {
|
) {
|
||||||
_, clientID, _, ok :=
|
sessionID, clientID, nick, ok :=
|
||||||
hdlr.requireAuth(writer, request)
|
hdlr.requireAuth(writer, request)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := request.Context()
|
||||||
|
|
||||||
err := hdlr.params.Database.DeleteClient(
|
err := hdlr.params.Database.DeleteClient(
|
||||||
request.Context(), clientID,
|
ctx, clientID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hdlr.log.Error(
|
hdlr.log.Error(
|
||||||
@@ -1389,12 +1392,79 @@ func (hdlr *Handlers) HandleLogout() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no clients remain, clean up the user fully:
|
||||||
|
// part all channels (notifying members) and
|
||||||
|
// delete the session.
|
||||||
|
remaining, err := hdlr.params.Database.
|
||||||
|
ClientCountForSession(ctx, sessionID)
|
||||||
|
if err != nil {
|
||||||
|
hdlr.log.Error(
|
||||||
|
"client count check failed", "error", err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if remaining == 0 {
|
||||||
|
hdlr.cleanupUser(
|
||||||
|
request, sessionID, nick,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
hdlr.respondJSON(writer, request,
|
hdlr.respondJSON(writer, request,
|
||||||
map[string]string{"status": "ok"},
|
map[string]string{"status": "ok"},
|
||||||
http.StatusOK)
|
http.StatusOK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cleanupUser parts the user from all channels (notifying
|
||||||
|
// members) and deletes the session.
|
||||||
|
func (hdlr *Handlers) cleanupUser(
|
||||||
|
request *http.Request,
|
||||||
|
sessionID int64,
|
||||||
|
nick string,
|
||||||
|
) {
|
||||||
|
ctx := request.Context()
|
||||||
|
|
||||||
|
channels, _ := hdlr.params.Database.
|
||||||
|
GetSessionChannels(ctx, sessionID)
|
||||||
|
|
||||||
|
notified := map[int64]bool{}
|
||||||
|
|
||||||
|
var quitDBID int64
|
||||||
|
|
||||||
|
if len(channels) > 0 {
|
||||||
|
quitDBID, _, _ = hdlr.params.Database.InsertMessage(
|
||||||
|
ctx, "QUIT", nick, "", nil, nil,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chanInfo := range channels {
|
||||||
|
memberIDs, _ := hdlr.params.Database.
|
||||||
|
GetChannelMemberIDs(ctx, chanInfo.ID)
|
||||||
|
|
||||||
|
for _, mid := range memberIDs {
|
||||||
|
if mid != sessionID && !notified[mid] {
|
||||||
|
notified[mid] = true
|
||||||
|
|
||||||
|
_ = hdlr.params.Database.EnqueueToSession(
|
||||||
|
ctx, mid, quitDBID,
|
||||||
|
)
|
||||||
|
|
||||||
|
hdlr.broker.Notify(mid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = hdlr.params.Database.PartChannel(
|
||||||
|
ctx, chanInfo.ID, sessionID,
|
||||||
|
)
|
||||||
|
|
||||||
|
_ = hdlr.params.Database.DeleteChannelIfEmpty(
|
||||||
|
ctx, chanInfo.ID,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = hdlr.params.Database.DeleteSession(ctx, sessionID)
|
||||||
|
}
|
||||||
|
|
||||||
// HandleUsersMe returns the current user's session info.
|
// HandleUsersMe returns the current user's session info.
|
||||||
func (hdlr *Handlers) HandleUsersMe() http.HandlerFunc {
|
func (hdlr *Handlers) HandleUsersMe() http.HandlerFunc {
|
||||||
return hdlr.HandleState()
|
return hdlr.HandleState()
|
||||||
@@ -1406,12 +1476,12 @@ func (hdlr *Handlers) HandleServerInfo() http.HandlerFunc {
|
|||||||
writer http.ResponseWriter,
|
writer http.ResponseWriter,
|
||||||
request *http.Request,
|
request *http.Request,
|
||||||
) {
|
) {
|
||||||
users, err := hdlr.params.Database.GetSessionCount(
|
users, err := hdlr.params.Database.GetUserCount(
|
||||||
request.Context(),
|
request.Context(),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hdlr.log.Error(
|
hdlr.log.Error(
|
||||||
"get session count failed", "error", err,
|
"get user count failed", "error", err,
|
||||||
)
|
)
|
||||||
hdlr.respondError(
|
hdlr.respondError(
|
||||||
writer, request,
|
writer, request,
|
||||||
|
|||||||
@@ -124,8 +124,16 @@ func (hdlr *Handlers) idleTimeout() time.Duration {
|
|||||||
return dur
|
return dur
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hdlr *Handlers) startCleanup(ctx context.Context) {
|
// startCleanup launches the idle-user cleanup goroutine.
|
||||||
cleanupCtx, cancel := context.WithCancel(ctx)
|
// We use context.Background rather than the OnStart ctx
|
||||||
|
// because the OnStart context is startup-scoped and would
|
||||||
|
// cancel the goroutine once all start hooks complete.
|
||||||
|
//
|
||||||
|
//nolint:contextcheck // intentional Background ctx
|
||||||
|
func (hdlr *Handlers) startCleanup(_ context.Context) {
|
||||||
|
cleanupCtx, cancel := context.WithCancel(
|
||||||
|
context.Background(),
|
||||||
|
)
|
||||||
hdlr.cancelCleanup = cancel
|
hdlr.cancelCleanup = cancel
|
||||||
|
|
||||||
go hdlr.cleanupLoop(cleanupCtx)
|
go hdlr.cleanupLoop(cleanupCtx)
|
||||||
@@ -161,12 +169,12 @@ func (hdlr *Handlers) runCleanup(
|
|||||||
) {
|
) {
|
||||||
cutoff := time.Now().Add(-timeout)
|
cutoff := time.Now().Add(-timeout)
|
||||||
|
|
||||||
deleted, err := hdlr.params.Database.DeleteStaleSessions(
|
deleted, err := hdlr.params.Database.DeleteStaleUsers(
|
||||||
ctx, cutoff,
|
ctx, cutoff,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hdlr.log.Error(
|
hdlr.log.Error(
|
||||||
"session cleanup failed", "error", err,
|
"user cleanup failed", "error", err,
|
||||||
)
|
)
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -174,7 +182,7 @@ func (hdlr *Handlers) runCleanup(
|
|||||||
|
|
||||||
if deleted > 0 {
|
if deleted > 0 {
|
||||||
hdlr.log.Info(
|
hdlr.log.Info(
|
||||||
"cleaned up stale clients",
|
"cleaned up stale users",
|
||||||
"deleted", deleted,
|
"deleted", deleted,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user