| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
| |
It is possible to use var chanPool = make(map[string](*chan string))
directly during declaration, even if it's a global variable. There is
no need to call a function to initialize it.
|
| |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
It is way too expensive to query the database every time we need to
check whether a user has chosen a course in a group. Since we can
(hopefully) guarantee that there is only one usable connection for any
given moment and user, we could store this data along with the
connection as a local variable in handleConn, which would be eligible
for garbage collection when handleConn exits. Here we create the
data structures that globally represent courseTypes and courseGroups,
and during the initial stages of handleConn, perform database queries to
populate userCourseGroups with the groups that the user has already
chosen.
Note that the HELLO command handler does similar database queries, and
as per the TODO listed in the comments, should be moved up here for
efficiency. (HELLO also serves as some sort of an initial connection
check; this should probably be replaced with WebSocket's native pings.)
|
|
|
|
| |
This is useful for testing whether connection collisions work well.
|
| |
|
| |
|
|
|
|
|
|
| |
The entry is deleted from cancelPool if and only if cancelPool's cancel
function for the user is equal to &newCancel. This compare-and-delete
works because of locking.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Cancel occurs in async. When the cancelled function exits, it deletes
the entry in cancelPool, and could replace the new routine's cancel
function, which is of course undesirable. Therefore this commit causes
handleConn to stop deleting from cancelPool entirely.
Note that this introduces a memory leak at the size of
context.CancelFunc per user, because we are never deleting from
cancelPool.
This does not actually solve the race condition documented in
fc911928 where two new connections start sufficiently close to each
other.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
When beginning to handle a WebSocket connection, we shall create a new
context, with the request context as its parent. We use the new context
for everything from now on, except for reporting error messages, which
is handled by creating a reportError function in a closure in
makeReportError and passing it along to command-handling functions.
Note that if one user attempts to rapidly launch WebSocket connections,
there might still be a race condition where the old connection hasn't
been completely established yet (i.e. an entry hasn't been added to
cancelPool) and the new connection isn't able to cancel the old
connection. I'm a bit tired to think of a solution now but it could
probably be solved by moving the cancellation registration and
cancelling sooner during the initial handshake.
Note that more parts of the event loop needs to select from ctx.Done
(from newCtx) for this to work reliably.
|
| |
|
| |
|
|
|
|
|
| |
Fake authentication is useful when benchmarking the server with an
external program without going insane with the OpenID Connect forms.
|
| |
|
| |
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
When a user has already chosen a course choice but sends a message to
choose it again, a database error occurs as the uniqueness constraint is
violated. In this case, we "reaffirm" the user's course choice, to
hopefully get their client's state back in sync.
However, we previously forgot to return back to the event loop after
this reaffirmation, causing the control flow to fall into the
course-increment and transaction-commit stage. The error is discovered
at tx.Commit, where pgx raises a ErrTxCommitRollback, because in that
case we're trying to commit a transaction that has already encountered
an error.
|
|
|
|
|
|
|
| |
When an unknown database error is encoutered, the connection should be
considered broken and the error should be reported as a fatal error to
the end user. Rejecting the course choice unsets the checkbox and lets
the page to be continued to be used, which is not intended.
|
|
|
|
|
|
| |
handleIndex and handleConn used to access the courses map without
RLock'ing coursesLock, which may cause issues if courses is being
written to, by a function such as setupCourses.
|
| |
|
|
|
|
|
|
|
|
| |
Laggy clients, extremely fast clicks, or other conditions could cause
duplicate course choices to be sent, which would violate the uniqueness
constraint. In this case the fact that the user has already chosen the
course should be reaffirmed, rather than making it look like their
choice was rejected.
|
|
|
|
|
|
| |
I think the new name better reflects the fact that it just ignores
failures when propagating to each channel. The old name sounds like
"this function itself could fail".
|
|
|
|
| |
References: https://todo.sr.ht/~runxiyu/cca/1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Note that this naive implementation currently allows users to submit
multiple requests for the same course. See the TODO comment below:
/*
* TODO: Ensure that the user is not already enrolled in this course
* and pay attention to relevant race conditions. It might be useful
* to restructure this part, to begin a transaction that adds the user
* to the database (and check the (currently not existing) uniqueness)
* constraint at that exact moment, and abort the transaction if the
* course limit is exceeded.
* Or perhaps choices should be also stored in an internal data
* structure, though that requires extra attention on consistency
* issues between the internal data structure and the database.
* (Sometime I should really go fix the LMDB bindings...)
*/
References: https://todo.sr.ht/~runxiyu/cca/1
|
| |
|
| |
|
| |
|
|
|
|
| |
Also use TryLock in setupChanPool, and fail when not successful.
|
| |
|
|
|
|
|
|
|
| |
One user shall only have one session at a time. This reduces the
possibility of strange race conditions and simplifies the code a lot.
References: https://todo.sr.ht/~runxiyu/cca/4
|
| |
|
|
|
|
|
|
|
| |
When propagate tries to propagate a message to a connection that
actually called propagate, it deadlocks because the it tries to send to
that connection's send channel in the same goroutine. This is an attempt
at a fix.
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
|
|
|
|
|
|
| |
A pointer to one could always be obtained via &chanPoolLock; but if I
declare it as a pointer globally, I would need to initialize it
somewhere so I don't get a null pointer dereference. It's more
convenient to just declare it as the value.
|