asami

Jakub Holý (HolyJak) 2022-12-24T23:36:04.892419Z

Hello! Any tips for troubleshooting why I am getting java.io.IOException: Stream Closed when trying to use a durable Asami? I did open it and believe never closed it. > at: > ... > asami.storage/eval9233/fn/G storage.cljc: 7 (repeats 2 times) > asami.durable.store.DurableConnection/db store.cljc: 248 > asami.durable.store/eval19730/db* store.cljc: 155 > asami.durable.store/eval19730/db*/fn store.cljc: 158 > asami.durable.flat-file.TxFile/latest flat_file.clj: 218 > asami.durable.flat-file/tx-file-size flat_file.clj: 193 > http://java.io.RandomAccessFile.getFilePointer RandomAccessFile.java Thank you!

✅ 1
quoll 2022-12-24T23:47:09.452329Z

I’m honestly at a loss, sorry

quoll 2022-12-24T23:47:52.888969Z

It looks like it’s been passed a closed file

quoll 2022-12-24T23:55:06.819969Z

Is there any chance you’ve used it with a with-open?

quoll 2022-12-24T23:59:42.211539Z

To explain what you’re seeing, you’re trying to access the “transaction file”. This is a file that contains a sequence of transaction records, and it’s instantiated in asami.durable.store (line 273).

quoll 2022-12-25T00:03:53.915909Z

Each transaction record contains the information about a commit point: • roots of the 3 triple-index trees • root of the data pool index (this is the structure that maps from strings/data/etc to ID numbers, and back) • The number of nodes, blocks, and pool nodes that have been used (this is because all of the index files are grown past what is needed, and these numbers allow the database to truncate to just what has been used). • The largest node ID allocated. • Timestamp This data makes a fixed size record, and the TxFile is just a sequence of these records.

quoll 2022-12-25T00:04:36.390309Z

All up, they are 9 long values. Meaning 9*8=72 bytes.

quoll 2022-12-25T00:05:19.333679Z

so the first transaction commit data is at position 0 in the file, the second is at offset 72, the third is at 144, etc

quoll 2022-12-25T00:05:53.223199Z

Every time a transaction is committed, then it goes to the end of this file and writes 72 bytes there.

quoll 2022-12-25T00:07:29.070879Z

tx-file-size is a function that gives you the size of the file. This is needed when it starts, so it knows where to append to. It’s also used when finding a transaction (say with the since function), because this gives the upper bound of the array to search in when looking for the transaction you’ve requested.

quoll 2022-12-25T00:12:36.514219Z

It appears that you’ve asked for the latest version of the database, so it’s called latest in https://github.com/quoll/asami/blob/0434e590dba60c5c561e5e62cbe6ea0b5e051f4b/src/asami/durable/flat_file.clj#L216 This has asked, “How big is the file?” so it can divide by the transaction size, getting the transaction number (starting at 0). This is going to be passed to get-tx to read in the data found at that transaction offset. e.g. if 3 transactions exist, then there will be 216 bytes. Divide by 72, and it now knows that there are 3 transactions, with the final one being transaction #2. That’s at offset 144 (2*72). So it will read that record.

quoll 2022-12-25T00:14:55.527959Z

BUT, it hasn’t got that far. Instead, it’s tried to call tx-file-size and failed. This failed on https://github.com/quoll/asami/blob/0434e590dba60c5c561e5e62cbe6ea0b5e051f4b/src/asami/durable/flat_file.clj#L193 when it attempted a .getFilePointer on the file

quoll 2022-12-25T00:15:41.035909Z

Oh… one other trick… the file pointer is ALWAYS at the end of the file. There is never a seek to somewhere in the middle of the file.

quoll 2022-12-25T00:16:22.725559Z

That’s because write operations always append, and the file position is used for this. But read operations are always done via a memory mapping of the file. So the file position never changes

quoll 2022-12-25T00:17:07.403019Z

I hope that didn’t overwhelm you with unnecessary background information.

quoll 2022-12-25T00:19:13.240059Z

Anyway… it comes back to where I started: latest has been called on this transaction file, but the file is already shut. If the TxFile object was closed by any other code, then something that thinks it owns the TxFile will think everything is fine, but the RandomAccessFile object underlying it will have been closed, and it won’t know

quoll 2022-12-25T00:21:15.135979Z

TxFiles belong to a asami.durable.store/DurableConnection so, if that was closed or released then it would have closed the underlying files, even though the object is still around

quoll 2022-12-25T00:22:29.586629Z

both delete-database and release (both are protocol functions for a Connection) will close the file

Jakub Holý (HolyJak) 2022-12-25T14:21:27.838359Z

Thanks a lot, I really appreciate understanding how things work. It turns out the problem was caused by incorrect integration with Mount, where :stop closed the files but subsequent :start has nat re-opened them. I have yet to find out what exactly I do wrong / how to do it right.

👍 1
Jakub Holý (HolyJak) 2022-12-25T14:52:33.156599Z

So on :start I do asami.core/connect and on :stop I do asami.core/shutdown . I guess that when I call 1. connect, 2. shutdown, 3. connect then the 2nd connect does not really do everything I would expect it, presumably some state survives from the previous connect call and thus it mistakenly concludes that there is no need to do all the initialisation and does not re-open the tx file. Any tips how to correct the situation? Looking into it I see that connect does first check if-let [conn (@connections uri)] and reading shutdown I see it only releases the connections but does not clear the connections atom so the subsequent connect won’t do anything.

quoll 2022-12-25T14:57:19.462129Z

The only global state values are: • file resources (the files that the process has open) • The global connection atom. This maps URIs to open connections. Maybe a shutdown isn’t removing the latter?

quoll 2022-12-25T14:57:39.034539Z

Oh… that’s exactly what you just said!

Jakub Holý (HolyJak) 2022-12-25T14:57:39.811239Z

Exactly ,see the updated ☝️

quoll 2022-12-25T14:58:19.813359Z

Hey, you caught me getting out of the shower! 🤣 About to go downstairs and wish my kids a merry Christmas

quoll 2022-12-25T14:59:51.463299Z

I guess delete-database and release either need handles back to the global connections map, or they shouldn’t be called directly

Jakub Holý (HolyJak) 2022-12-25T15:00:02.670639Z

Sorry for that 🙂 Please ignore me and go enjoy your kids, and merry Christmas to you all!

🎄 1
quoll 2022-12-25T15:01:16.819929Z

Merry Christmas

Jakub Holý (HolyJak) 2022-12-25T15:01:18.475529Z

I guess delete-database actually deletes the files from the disk, no? I do not want to do that, I want the data to persist 🙂

quoll 2022-12-25T15:01:42.027619Z

Yes, but the connection map has to be updated!

quoll 2022-12-25T15:01:56.735719Z

Even though that’s not a function you need

Jakub Holý (HolyJak) 2022-12-25T15:02:21.100819Z

Yes. so there should be likely a function that both does shutdown AND resets the connections map.

Jakub Holý (HolyJak) 2022-12-25T15:03:05.422349Z

I realized I can solve my problem simply by not calling shutdown from Mount, and instead rely on the jvm shutdown hook that calls it upon JVM exit.

quoll 2022-12-25T18:10:01.765639Z

I will try to get a Boxing Day release done 🙂

🙏 1
quoll 2023-01-27T20:20:50.640629Z

> Looking into it I see that connect does first check if-let [conn (@connections uri)] and reading shutdown I see it only releases the connections but does not clear the connections atom so the subsequent connect won’t do anything. This meeting mostly doesn’t need me, so I checked in on this. I’m now removing the connections during shutdown, but I will explain: the shutdown operation was specifically to be called during shutdown. Nothing should attempt to access anything at all after shutdown is called. In fact, I didn’t expect anyone to call shutdown. You’ll see that it’s only used as a JVM shutdown hook.

👍 1
quoll 2023-01-27T20:30:46.512729Z

> I guess delete-database actually deletes the files from the disk, no? I do not want to do that, I want the data to persist 🙂 I was focused the connection map when this came in, so I forgot to say… If you want to close everything but not delete the files, then the operation you were looking for was release. delete-database does what the name says.

👍 1
Jakub Holý (HolyJak) 2023-01-27T20:46:16.671809Z

Thank you! My use case is using clojure.tools.namespace.repl/refresh to reload my code and thus I need to cleanly release stuff before it and re-initialize it afterwards. I have changed my Mount use so that it only starts Asami and does nothing on shutdown, and is marked with ^{:on-reload :noop} so that tools-ns doesn’t mess with it. This seems to be working fine.

quoll 2023-01-27T20:47:46.911009Z

Well, it’s safer to do it the what I’ve changed it, but yeah… I specifically intended shutdown for JVM shutdown. I never really expected anyone to try to use it to clear things. release was different though, and that needed to be fixed

👍 1
quoll 2023-01-03T16:28:07.920719Z

I did not manage this. (Sorry!) I am hoping for something soon, but between not turning on the computer (which was necessary for my health!) and lingering health issues I just didn’t get to it

quoll 2023-01-03T16:28:24.321779Z

Soon though. I need to incorporate Raphael into Asami, and I’ll release when I do

👍 1