diff options
-rw-r--r-- | courses.go | 11 | ||||
-rw-r--r-- | sem.go | 40 | ||||
-rw-r--r-- | ws.go | 65 |
3 files changed, 110 insertions, 6 deletions
@@ -37,6 +37,11 @@ type courseT struct { /* * TODO: There will be a lot of lock contention over Selected. It is * probably more appropriate to directly use atomics. + * Except that it's actually hard to use atomics directly here + * because I need to "increment if less than Max"... I think I could + * just do compare and swap in a loop, but the loop would be intensive + * on the CPU so I'd have to look into how mutexes/semaphores are + * actually implemented and how I could interact with the runtime. */ Selected int SelectedLock sync.RWMutex @@ -46,6 +51,8 @@ type courseT struct { Group courseGroupT Teacher string Location string + Usems map[string](*usemT) + UsemsLock sync.RWMutex } const ( @@ -138,7 +145,9 @@ func setupCourses() error { } break } - currentCourse := courseT{} //exhaustruct:ignore + currentCourse := courseT{ + Usems: make(map[string]*usemT), + } //exhaustruct:ignore err = rows.Scan( ¤tCourse.ID, ¤tCourse.Max, @@ -0,0 +1,40 @@ +/* + * Additional synchronization routines + * + * Copyright (C) 2024 Runxi Yu <https://runxiyu.org> + * SPDX-License-Identifier: AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +package main + +type usemT struct { + _ch (chan struct{}) +} + +func (s *usemT) init() { + s._ch = make(chan struct{}, 1) +} + +func (s *usemT) set() { + select { + case s._ch <- struct{}{}: + default: + } +} + +func (s *usemT) ch() (<- chan struct{}) { + return s._ch +} @@ -313,6 +313,7 @@ func handleConn( cancel := cancelPool[userID] if cancel != nil { (*cancel)() + /* TODO: Make the cancel synchronous */ } cancelPool[userID] = &newCancel }() @@ -344,17 +345,58 @@ func handleConn( delete(chanPool, userID) }() + /* TODO: Tell the user their current choices here. Deprecate HELLO. */ + + usems := make(map[int]*usemT) + func() { + coursesLock.RLock() + defer coursesLock.RUnlock() + for courseID, course := range courses { + var usem usemT + func() { + course.UsemsLock.Lock() + defer course.UsemsLock.Unlock() + course.Usems[userID] = &usem + }() + usems[courseID] = &usem + } + }() + defer func() { + coursesLock.RLock() + defer coursesLock.RUnlock() + for _, course := range courses { + func() { + course.UsemsLock.Lock() + defer course.UsemsLock.Unlock() + delete(course.Usems, userID) + }() + } + }() + + usemParent := make(chan int) + for courseID, usem := range usems { + go func() { + for { + select { + case <-newCtx.Done(): + return + case <-usem.ch(): + select { + case <-newCtx.Done(): + return + case usemParent <- courseID: + } + } + } + }() + } + /* * userCourseGroups stores whether the user has already chosen a course * in the courseGroup. */ var userCourseGroups userCourseGroupsT = make(map[courseGroupT]bool) populateUserCourseGroups(newCtx, &userCourseGroups, userID) - /* - * TODO: No more HELLO command needed? Or otherwise integrate the two. - * In any case, the database work is being duplicated. Probably move - * that up here. - */ /* * Later we need to select from recv and send and perform the @@ -434,6 +476,19 @@ func handleConn( * be closed, and the user would see the connection * closed page which should explain it. */ + case courseID := <-usemParent: + var selected int + func() { + course := courses[courseID] + course.SelectedLock.RLock() + defer course.SelectedLock.RUnlock() + selected = course.Selected + }() + err := writeText(newCtx, c, fmt.Sprintf("M %d %d", courseID, selected)) + if err != nil { + return fmt.Errorf("error sending to websocket for course selected update: %w", err) + } + continue case gonnasend := <-send: err := writeText(newCtx, c, gonnasend) if err != nil { |