TransactionalDOWrapper suggestions thread

Cool idea 👍 ! I recommend checking out do-transactional-outbox for an even stronger model of storage consistency. For your example in the blog post, I'm not sure if by using await you potentially eliminate (some of) the benefits of caching state in memory, let me explain. I recommend taking a look at state.blockConcurrencyWhile: call it in your constructor, no need to await it (you couldn't anyways, since constructors have to be sync), but it blocks further requests until you finish with your hydrate function. Then, you can always be sure that this.name and others will have been populated whenever your request comes in via fetch, so you can simplify your code further - eliminating another potential concurrency issue, where you're awaiting the hydrate promise, and another event handler starts executing, calling hydrate again, and potentially reading from storage once more as this.hydrated may not have been set to true yet (knock knock, race condition, who's there type situation), and then you overwrite name from the second call. Basically, use blockConcurrencyWhile in the constructor, move hydrate to within that call and remove it from fetch. Oh, small note, you could reduce boilerplate by exporting a function instead of a class, which applies the argument as the static class and returns that as the DO class for the runtime workers
16 Replies
Larry
Larry3y ago
OK. There is a lot here to unpack so I'm starting this thread... The reason I use await this.hydrate() instead of state.blockConcurrencyWhile is that in a real (not toy) DO, I do all of my input checking (Accept and Content-Type headers, body well composed, etc.) before rehydrating. Also, I have a few "static" endpoints inside my DOs that don't need to be hydrated at all. I didn't quite follow your explanation that using await potentially eliminates benefits of caching memory in state. Can you elaborate more or did my explaination for my await hydrate() approach address your worries? I also am uncertain about your "eliminating another potential concurrency issue". I get the impression that input gates give me all the protection I need for that, but I could be wrong about that. I think I follow your suggestion about making it a function instead of a Class and removing boilderplate but let me ask something to confirm. The boilerplate that would be eliminated would be the requirement for this.TheClass, right? I am familiar with transactional outbox patterns for microservices and I considered an approach like this for this need although I never found do-transactional-outbox. I will probably still employ that approach if I ever have any need to send messages from my DOs but for now, I don't have that need. The approach that I posted today has the advantage that it requires no changes in your existing DOs, but using do-transactional-outbox does, right? This is going back a few comments, but my example using hydrate() is not a requirement for using the approach I posted today. I will add a note explaining that, and maybe get rid of it if it becomes a distraction like it may have in your case. Ohhh, and THANKS for your feedback!!!
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
Larry
Larry3y ago
Don't input gaits protect you from two requests attempting to hydrate at the same time?
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
Larry
Larry3y ago
I am only doing storage operations... or are you saying that the await in my await hydrate() gives a chance for another request to come in?
DaniFoldi
DaniFoldiOP3y ago
Yup, when you await a promise, it gets put into the microtask queue, and if there was another task in there, it will be executed first. See https://developer.mozilla.org/en-US/docs/Web/API/HTML_DOM_API/Microtask_guide/In_depth#tasks_vs_microtasks , warning it gets technical or boring, depending on your level of nerdiness. The way I like to think about it is async is just a label to show something returns a promise, and await wraps the rest of the code in a big .then() that is hidden from you for code readability reasons. But execution pauses when you await something, for example fetch() something, or read from DO storage. Other requests may be executed and even return before the first one does.
In depth: Microtasks and the JavaScript runtime environment - Web A...
When debugging or, possibly, when trying to decide upon the best approach to solving a problem around timing and scheduling of tasks and microtasks, there are things about how the JavaScript runtime operates under the hood that may be useful to understand.
Larry
Larry3y ago
Aww rats! but thanks! I'm mostly convinced, but @kenton can you confirm that any other await (even for a function that only has storage operations) nullifies the input gate protection? ... except an external fetch, which you already wrote does not.
kenton
kenton3y ago
I'm not sure if I understand the question. The input gate only opens when you await a non-storage external I/O event, like a fetch(). The input gate will not open if you await a promise that resolves without waiting for any external I/O. The microtask queue will run, but external I/O will not arrive. if there are other tasks in the microtask queue, they may run, yes. However, new events triggered from the outside are prevented from being delivered to the microtask queue. So anything in the queue is something you must have put there.
DaniFoldi
DaniFoldiOP3y ago
Doesn't awaiting on a promise that's not the storage operation itself, for example something from crypto also open the input gate?
Larry
Larry3y ago
Here is a minimal example
Larry
Larry3y ago
@kenton Does the above call to await this.hydrate() open the gate or is it a safe alternative to state.blockConcurrencyWhile for lazy loading from storage.
kenton
kenton3y ago
No. Only awaiting external I/O counts. Crypto promises are fake, the crypto is actually done synchronously and the promise completes immediately. (If we ever decided to offload crypto to another thread we would make sure it doesn't open the input gate to avoid breaking assumptions.) no, this will not open the input gate, because hydrate() does not do any I/O other than stroage.
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
Larry
Larry3y ago
@crabmusket , I totally appreciate your jumping in and helping. Most (maybe all except Kenton?) are uncertain what is safe and what is not. We're figuring it out together. I love it. I'm hoping you would be willing to let me post your external fetch gate (with full attribution).
Larry
Larry3y ago
Medium
Lazy hydration
When you keep state in your durable object instance properties, this is a patten to lazy rehydrate whenever your DO is reinstantiated.
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View

Did you find this page helpful?