summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--courses.go11
-rw-r--r--sem.go40
-rw-r--r--ws.go65
3 files changed, 110 insertions, 6 deletions
diff --git a/courses.go b/courses.go
index b3d3a9c..815c85b 100644
--- a/courses.go
+++ b/courses.go
@@ -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(
&currentCourse.ID,
&currentCourse.Max,
diff --git a/sem.go b/sem.go
new file mode 100644
index 0000000..c587e61
--- /dev/null
+++ b/sem.go
@@ -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
+}
diff --git a/ws.go b/ws.go
index 7ec6a78..7966560 100644
--- a/ws.go
+++ b/ws.go
@@ -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 {