2026-10-26 Oddμ code churn =========================== When I rewrote Oddμ to use SQLite for full-text search, I had to update the database when changes were made. Once the code was there, it was easy to add more information, like links. That in turned allowed me to use the database when rendering the page to HTML. Now it was possible to quickly find all the backlinks to a page. At the time, the easiest solution was to add a global variable for the database handle. The problem is testing, when tests run in parallel. One test starts putting a bunch of pages into the database. Another test does the same and reads the pages in the database. And suddenly there are more pages in there. There are ways around that. The problem is not new because creating files and reading all the files runs into the same problem. The first step was to have every test use its own directory and cleaning up when the test is done. But what about entries in the database. They weren't always getting cleaned out. And depending on the random order of tests, I'd run into a problem. Of course the next time I'd run the tests, the problem disappeared. Something about the first test having to initialize the database and no other test. Some of the tests probably don't initialize the database and if they run first, rarely, things fail. So the solution, it seems to me, is to have a wiki context that contains the database handle and pass that around: 1. when the server starts, create the context and pass it to all the handlers as they are registered; 2. when commands run, create the context and pass it to the data structure initialized when commands are registered; 3. when tests run, create a new directory and a database for every test and clean it up when the test is done. I've done 1 and 2 but 3 is still open. Remember kids: Write code such that it can be tested. Avoid global state. Collect all the state you have into a context object and pass that around. I know it hurts but global state will hurt you more when you're writing tests. #Oddμ #Programming 2026-10-28. And it is done! What a nightmare. I think what I really did not appreciate where things where the compile didn't reassure me. When the server runs, this context is created once, the database is opened once, every handler gets a copy, done. But then I realized that the code sometimes created page-like objects using literals, e.g. &Page{Name: "foo", Body: []byte("bar")} -- and it is all too easy to forget that this now needs the context! I needed to change that to a "constructor" called newPage and pass along the context. The compiler can check whether I pass the context to the constructor but it cannot prevent me from creating page-like objects using literals and then forgetting about the context. When commands, the context is created once and passed to each command structure as it is registered. Was this the right call? It looks awkward. Perhaps each command should create the context? No, that doesn't work either because commands can be interrupted (like the server) and in that case I want to close the database. This is why the context is set up before calling the individual commands: This makes it possible to have one signal handler to handle one context object with one database handle. At first I thought many of the commands didn't use the database so did not need the context but I was wrong. The context needs to be a reference of every page-like object because the templates just use the page-like object and if the action requires access to the database, that means the page-like object must have access to the database handle. And therefore a command can only do without the context if it doesn't access the database and when it doesn't execute a template. In the end, there are very few commands like that: displaying the version, showing manual pages and similar commands. Tests now run around nine to ten seconds instead of the previous three to four seconds because a new context for every test means a new, empty database for every test. 2026-10-29. OK, this problem is probably older and unrelated to the new code, but I just noticed the following: If you upload a file, Oddμ proposes a filename like image-1.jpg. After the upload, it auto-increments the number and proposes a filename like image-2.jpg for the upload. This is great. If you change the filename to 2026-10-26-oddmu-testing.jpg, it works but then Oddμ still auto-increments the number! 😨 And so it proposes 2026-10-27-oddmu-testing.jpg for the next upload. This is not great. (Now fixed.) 2026-11-01. And now another problem. I've noticed that when I "add" to a page, as I often do, the wiki crashes. And the watcher also crashes when a Markdown file is to be removed (although I don't remember attempting to do this). Today I have seen multiple instances. 1. add_append.go:33 to page.go:61 to index.go:395, 2. watch.go:276 to index.go:445. 2026-11-02. Sadly, I cannot replicate it at home. Something I just realized is that Oddmu crashes after saving the page. If you resubmit the form after Oddmu recovers, the addition is duplicated. When looking at the backtraces, one realizes that they continue into the database library. For example right now I'm looking at a backtrace that goes from main.(*Page).persist → database/sql.(*DB).Begin → database/sql.(*DB).BeginTx → database/sql.(*DB).retry → database/sql.(*DB).BeginTx.func1 → database/sql.(*DB).begin → database/sql.(*DB).conn → sync.(*Mutex).Lock → internal/sync.(*Mutex).Lock → panic and then I'm realizing, this is not my database handle that is missing, this is some internal error with the locking resulting in crash. I'm guessing I no longer want to search for bugs in my code but I'm expecting somebody to have introduced bugs. And I look at my list of dependencies and there's SQLite with its AI taint. Let's check its CHANGES.md. It seems to me that everything seemed pretty calm until 2026-09-15 with v1.59.0. And then changes started picking up: 2026-09-28 v1.60.0, 2026-09-29 v1.60.1, three weeks of calm, then 2026-10-21 v1.61.0, 2026-10-31 v1.61.1 -- perhaps it's time to go back in time for a moment? I thought about switching back to v1.59.0 but there was an immediate crash. So I'm reverting that. We're staying with the latest release. At the same time, I see two crashes inside the watcher where it goes into main.(*watchStore).watchDoRemove → main.(*Page).delete and crashes. So something else is going on. Is this related to the Mutex? Why is the watcher even reacting? When saving a file, the old file is renamed to the backup before the new file is saved. In this situation, the watcher should be ignoring the file. Just now, however: Nov 02 19:54:35 paraelectrobombus oddmu[4139097]: 2026/11/02 19:54:35 Deindex 2026-10-26-oddmu-testing.md Nov 02 19:54:35 paraelectrobombus oddmu[4139097]: [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0xae9006] In the same second! And it's in the delete code: main.(*Page).delete(0x2d32bd211b80) main.(*watchStore).watchDoRemove(0x13b9f60, {0x2d32bd522042, 0x1b}) And now I see the problem. That p.delete gets a fake Page without wiki set. Big problem! Another problem, however, is that this should not be called in the first place. 2026-11-02. This needs more logging. Luckily, the watcher reacts to the ODDMU_DEBUG variable being set. Let's see how much logging this will get me. 2026-11-03. Just now I edited the page and found a situation that looked like it would have crashed the wiki before yesterday's change: 09:03:54 Ignoring 2026-10-26-oddmu-testing.md for 1 s 09:03:55 Unignoring 2026-10-26-oddmu-testing.md 09:03:57 WRITE event for ./index.db-wal is unhandled (multiple times) 09:03:57 Deindex 2026-10-26-oddmu-testing.md 09:03:57 Save 2026-10-26-oddmu-testing by alex Notice how seconds pass before the page is renamed and deindexed by the watcher. What causes this delay? It takes seconds for SQLindex to start updating the database, that's for sure. Next, the wiki writes the list of changes and this time there is a chmod event? Strange. 09:03:57 Ignoring changes.md for 1 s 09:03:57 Skipping event for ignored changes.md 09:03:57 CHMOD event for ./changes.md~ is unhandled 09:03:57 Skipping event for ignored changes.md 09:03:57 Skipping event for ignored changes.md 09:03:58 Unignoring changes.md In any case, since the 2026-10-26-oddmu-testing is not ignored, the watcher started picking it up. The following changes happen at about the same time and show up interleaved with the log messages above. I separated the two for clarity. The watcher deindexed the page above and now it keeps waiting for 0.5 seconds until the writing stops. And then it updates the index, again. 09:03:57 CHMOD event for ./2026-10-26-oddmu-testing.md~ is unhandled 09:03:57 Waiting 0.5 s for 2026-10-26-oddmu-testing.md events to stop 09:03:57 Waiting 0.5 s for 2026-10-26-oddmu-testing.md events to stop 09:03:57 Waited 0.5 s for 2026-10-26-oddmu-testing.md 09:03:57 Update for 2026-10-26-oddmu-testing.md 09:03:57 Update index for 2026-10-26-oddmu-testing.md 09:03:58 Waited 0.5 s for 2026-10-26-oddmu-testing.md I'm not sure what the best course of action is. I'm thinking extending the "ignore" period from 1s to 5s might be appropriate. And I should see whether DB access happens between the beginning of the ignore period and the writing of the files. If so, DB access should be moved out of there. Let's see how it goes.