Posts in Tutorial (20 found)

Bending Emacs - Episode 7: Eshell built-in commands

With my recent rinku post and Bending Emacs episode 6 both fresh in mind, I figured I may as well make another Bending Emacs episode, so here we are: Bending Emacs Episode 7: Eshell built-in commands Check out the rinku post for a rundown of things covered in the video. Liked the video? Please let me know. Got feedback? Leave me some comments . Please go like my video , share with others, and subscribe to my channel . If there's enough interest, I'll continue making more videos! Enjoying this content or my projects ? I am an indie dev. Help make it sustainable by ✨ sponsoring ✨ Need a blog? I can help with that . Maybe buy my iOS apps too ;)

0 views
xenodium Yesterday

Rinku: CLI link previews

In my last Bending Emacs episode, I talked about overlays and used them to render link previews in an Emacs buffer. While the overlays merely render an image, the actual link preview image is generated by rinku , a tiny command line utility I built recently. leverages macOS APIs to do the actual heavy lifting, rendering/capturing a view off screen, and saving to disk. Similarly, it can fetch preview metadata, also saving the related thumbnail to disk. In both cases, outputs to JSON. By default, fetches metadata for you. In this instance, the image looks a little something like this: On the other hand, the flag generates a preview, very much like the ones you see in native macOS and iOS apps. Similarly, the preview renders as follows: While overlays is one way to integrate anywhere in Emacs, I had been meaning to look into what I can do for eshell in particular. Eshell is just another buffer , and while overlays could do the job, I wanted a shell-like experience. After all, I already knew we can echo images into an eshell buffer . Before getting to on , there's a related hack I'd been meaning to get to for some time… While we're all likely familiar with the cat command, I remember being a little surprised to find that offers an alternative elisp implementation. Surprised too? Go check it! Where am I going with this? Well, if eshell's command is an elisp implementation, we know its internals are up for grabs , so we can technically extend it to display images too. is just another function, so we can advice it to add image superpowers. I was pleasantly surprised at how little code was needed. It basically scans for image arguments to handle within advice and otherwise delegates to 's original implementation. And with that, we can see our freshly powered-up command in action: By now, you may wonder why the detour when the post was really about ? You see, this is Emacs, and everything compounds! We can now leverage our revamped command to give similar superpowers to , by merely adding an function. As we now know, outputs things to JSON, so we can use to parse the process output and subsequently feed the image path to . can also output link titles, so we can show that too whenever possible. With that, we can see the lot in action: While non-Emacs users are often puzzled by how frequently we bring user flows and integrations on to our beloved editor, once you learn a little elisp, you start realising how relatively easily things can integrate with one another and pretty much everything is up for grabs . Reckon and these tips will be useful to you? Enjoying this blog or my projects ? I am an 👉 indie dev 👈. Help make it sustainable by ✨ sponsoring ✨ Need a blog? I can help with that . Maybe buy my iOS apps too ;)

0 views
fLaMEd fury 2 days ago

Contain The Web With Firefox Containers

What’s going on, Internet? While tech circles are grumbling about Mozilla stuffing AI features into Firefox that nobody asked for (lol), I figured I’d write about a feature people might actually like if they’re not already using it. This is how I’m containing the messy sprawl of the modern web using Firefox Containers. After the ability to run uBlock Origin, containers are easily one of Firefox’s best features. I’m happy to share my setup that helps contain the big bad evil and annoying across the web. Not because I visit these sites often or on purpose. I usually avoid them. But for the moments where I click something without paying attention, or I need to open a site just to get a piece of information and failing (lol, login walls), or I end up somewhere I don’t wanta to be. Containers stop that one slip from bleeding into the rest of my tabs. Firefox holds each site in its own space so nothing spills into the rest of my browsing. Here’s how I’ve split things up. Nothing fancy. Just tidy and logical. Nothing here is about avoiding these sites forever. It’s about containing them so they can’t follow me around. I use two extensions together: MAC handles the visuals. Containerise handles the rules. You can skip MAC and let Containerise auto create containers, but you lose control over colours and icons, so everything ends up looking the same. I leave MAC’s site lists empty so it doesn’t clash with Containerise. Containerise becomes the single source of truth. If I need to open something in a specific container, I just right click and choose Open in Container. Containers don’t fix the surveillance web, but they do reduce the blast radius. One random visit to Google, Meta, Reddit or Amazon won’t bleed into my other tabs. Cookies stay contained. Identity stays isolated. Tracking systems get far less to work with. Well, that’s my understanding of it anyway. It feels like one of the last features in modern browsers that still puts control back in the user’s hands, without having to give up the open web. Just letting you know that I used ChatGPT (in a container) to help me create the regex here - there was no way I was going to be able to figure that out myself. So while Firefox keeps pandering to the industry with AI features nobody asked for (lol), there’s still a lot to like about the browser. Containers, uBlock Origin, and the general flexibility of Firefox still give you real control over your internet experience. Hey, thanks for reading this post in your feed reader! Want to chat? Reply by email or add me on XMPP , or send a webmention . Check out the posts archive on the website. Firefox Multi Account Containers (MAC) for creating and customising the containers (names, colours, icons). Containerise for all the routing logic using regex rules.

0 views
Andre Garzia 3 days ago

My chocolate truffles recipe

# My Brigadeiro Recipe (chocolate truffles) This is a very traditional recipe from Brazil that is a staple of birthday parties all over the country. The ingredients are: * 1 tin of condensed milk (395g) * 1/2 of a cup of cream (100g) * 1/2 tablespoon of butter (15g) * 1 and a half tablespoon of cocoa powder (23g) Mix everything on a pan until they are well mixed. Turn the heat on in medium and mix until you can use a spatula to separate the mix and when it comes back together it folds from the top like a wave. Be aware that the mixture will rise in the pot before going down again. If you have oreos, you can make little bats for halloween as seen in the photo below. ![](/2025/11/img/3f29fa74-c6a2-45c0-8332-fa77772c25ab.jpg)

0 views
xenodium 3 days ago

Bending Emacs - Episode 6: Overlays

The Bending Emacs series continues with a new a new episode. Bending Emacs Episode 6: Overlays Today we had a quick intro to overlays. Here's the snippet I used for adding snippets: Similarly, this is what we used for removing the overlay. Of the experiments, you can find: Hope you enjoyed the video! Liked the video? Please let me know. Got feedback? Leave me some comments . Please go like my video , share with others, and subscribe to my channel . If there's enough interest, I'll continue making more videos! Enjoying this content or my projects ? I am an indie dev. Help make it sustainable by ✨ sponsoring ✨ Need a blog? I can help with that . Maybe buy my iOS apps too ;) Redaction snippet at the related blog post . Dired media metadata at Ready Player's ready-player-dired.el . Link previews: While I don't have elisp to share for link previews just yet, I did release a tiny thumbnail utility named rinku ;)

0 views
Brain Baking 4 days ago

Rendering Your Java Code Less Error Prone

Error Prone is Yet Another Programming Cog invented by Google to improve their Java build system. I’ve used the multi-language PMD static code analyser before (don’t shoot the messenger!), but Error Prone takes it a step further: it hooks itself into your build system, converting programming errors as compile-time errors. Great, right, detecting errors earlier, without having to kick an external process like PMD into gear? Until you’re forced to deal with hundreds of errors after enabling it: sure. Expect a world of hurt when your intention is to switch to Error Prone just to improve code linting, especially for big existing code bases. Luckily, there’s a way to gradually tighten the screw: first let it generate a bunch of warnings and only when you’ve tackled most of them, turn on Error! Halt! mode. When using Gradle with multiple subprojects, things get a bit more convoluted. This mainly serves as a recollection of things that finally worked—feeling of relief included. The root file: The first time you enable it, you’ll notice a lot of nonsensical errors popping up: that’s what that is for. We currently have the following errors disabled: Error Prone’s powerful extendability resulted in Uber picking up where Google left off by releasing NullAway , a plug-in that does annotation-based null checking fully supporting the JSpecify standard . That is, it checks for stupid stuff like: JSpecify is a good attempt at unifying these annotations—last time I checked, IntelliJ suggested auto-importing them from five different packages—but the biggest problem is that you’ll have to dutifully annotate where needed yourself. There are OpenRewrite JSpecify recipes available to automatically add them but that won’t even cover 20% of the cases, as when it comes to manual if null checks and the use of , NullAway is just too stupid to understand what your intentions are. NullAway assumes non-null by default. This is important, because in Java object terminology, everything is nullable by default. You won’t need to add a lot of annotations, but adding has a significant ripple effect: if that’s nullable, then the object calling this object might also be, which means I should add this annotation here and here and here and here and here and… Uh oh. After 100 compile errors, Gradle gives up. I fixed 100 errors, recompiled, and 100 more appeared. This fun exercise lasted almost an entire day until I was the one giving up. The potential commit touched hundreds of files and added more bloat to an already bloated (it’s Java, remember) code base I’ve ever seen. Needless to say, we’re currently evaluating our options here. I’ve also had quite a bit of trouble picking the right combination of plug-ins for Gradle to get this thing working. In case you’d like to give it a go, extend the above configuration with: You have to point NullAway to the base package path ( ) otherwise it can’t do its thing. Note the configuration: we had a lot of POJOs with private constructors that set fields to while they actually cannot be null because of serialisation frameworks like Jackson/Gson. Annotate these with and NullAway will ignore them. If you thought fixing all Error Prone errors was painful, wait until you enable NullAway. Every single statement needs its annotation. OpenRewrite can help, but up to a point, as for more complicated assignments you’ll need to decide for yourself what to do. Not that the exercise didn’t bear any fruit. I’ve spotted more than a few potential mistakes we made in our code base this way, and it’s fun to try and minimize nullability. The best option of course is to rewrite the whole thing in Kotlin and forget about the suffix. All puns aside, I can see how Error Prone and its plug-ins can help catch bugs earlier, but it’s going to come at a cost: that of added annotation bloat. You probably don’t want to globally disable too many errors so is also going to pop up much more often. A difficult team decision to make indeed. Related topics: / java / By Wouter Groeneveld on 25 November 2025.  Reply via email . —that’s a Google-specific one? I don’t even agree with this thing being here… —we’d rather have on every line next to each other —we can’t update to JDK9 just yet —we’re never going to run into this issue —good luck with fixing that if you heavily rely on reflection

0 views

Demystifying Determinism in Durable Execution

Determinism is a key concept to understand when writing code using durable execution frameworks such as Temporal, Restate, DBOS, and Resonate. If you read the docs you see that some parts of your code must be deterministic while other parts do not have to be.  This can be confusing to a developer new to these frameworks.  This post explains why determinism is important and where it is needed and where it is not. Hopefully, you’ll have a better mental model that makes things less confusing. We can break down this discussion into: Recovery through re-execution. Separation of control flow from side effects. Determinism in control flow Idempotency and duplication tolerance in side effects This post uses the term “control flow” and “side effect”, but there is no agreed upon set of terms across the frameworks. Temporal uses “workflow” and “activity” respectively. Restate uses the terms such as “handler”,  “action” and “durable step”. Each framework uses different vocabulary and have varying architectures behind them. There isn’t a single overarching concept that covers everything, but the one outlined in this post provides a simple way to think about determinism requirements in a framework agnostic way. Durable execution takes a function that performs some side effects, such as writing to a database, making an API call, sending an email etc, and makes it reliable via recovery (which in turn depends on durability). For example, a function with three side effects: Step 1, make a db call. Step 2, make an API call. Step 3, send an email. If step 2 fails (despite in situ retries) then we might leave the system in an inconsistent state (the db call was made but not the API call). In durable execution, recovery consists of executing the function again from the top, and using the results of previously run side effects if they exist. For example, we don’t just execute the db call again, we reuse the result from the first function execution and skip that step. This becomes equivalent to jumping to the first unexecuted step and resuming from there. Fig 1. A function is retried, using the results of the prior partial execution where available. So, durable execution ensures that a function can progress to completion via recovery, which is a retry of the function from the top. Resuming from where we left off involves executing the code again but using stored results where possible in order to resume from where it failed. In my Coordinated Progress model, this is the combination of a reliable trigger and progressable work . A function is a mix of executing control flow and side effects. The control flow itself may include state, and branches (if/then/else) or loops execute based on that state. The control flow decides which side effects to execute based on this looping and branching. Fig 2. Control flow and side effects In Temporal, the bad_login function would be a workflow and the block_account and send_warning_email would be activities . The workflow and activity work is separated into explicit workflow and activity tasks, possibly run on different workers. Other frameworks simply treat this as a function and wrap each side effect to make it durable. I could get into durable promises and continuations but that is a topic I will cover in a future post. So let’s look at another example. First we retrieve a customer record, then we check if we’re inside of the promo end date, if so, charge the card with a 10% discount, else charge the full amount. Finally send a receipt email. This introduces a bug that we’ll cover in the next section. Fig 3. process_order function as a mix of control flow (green) and side effects (grey) Durable execution treats the control flow differently from the side effects, as we’ll see in sections 3 and 4. Determinism is required in the control flow because durable execution re-executes code for recovery. While any stored results of side effects from prior executions are reused, the control flow is executed in full. Let’s look at an example: Fig 4. Double charge bug because of a non-deterministic if/else In the first execution, the current time is within the promo date, so the then-branch is executed, charging the card with the discount. However, on the second invocation, the current time is after the promo end date, causing the else-branch to execute, double charging the customer. Fig 5. A non-deterministic control flow causes a different branch to execute during the function retry. This is fixed by making the now() deterministic by turning it into a durable step whose result is recorded. Then the second time it is executed, it returns the same datetime (it becomes deterministic). The various SDKs provide deterministic dates, random numbers and UUIDs out of the box. Another fun example is if we make the decision based on the customer record retrieved from the database. In this variant, the decision is made based on the loyalty points the customer currently has. Do you see the problem? If the send email side effect fails, then the function is retried. However, the points value of the order was deducted from the customer in the last execution, so that in execution 2, the customer no longer has enough loyalty points! Therefore the else-branch is executed, charging their credit card! Another double payment bug. We must remember that the durable function is not an atomic transaction. It could be considered a transaction which has guarantees around making progress, but not one atomic change across systems. We can fix this new double charge bug by ensuring that the same customer record is returned on each execution. We can do that by treating the customer record retrieval as a durable step whose result will be recorded. Fig 6. Make the customer retrieval deterministic if the control flow depends on it. Re-execution of the control flow requires determinism: it must execute based on the same decision state every single time and it must also pass the same arguments to side effect code every single time. However, side effects themselves do not need to be deterministic, they only require idempotency or duplication tolerance. Durable execution re-executes the control flow as many times as is needed for the function to make progress to completion. However, it typically avoids executing the same side effects again if they were previously completed. The result of each side effect is durably stored by the framework and a replay only needs the stored result. Therefore side effects do not need to be deterministic, and often that is undesirable anyway. A db query that retrieves the current number of orders or the current address of a customer may return a different result every time. That’s a good thing, because the number of orders might change, and an address might change. If the control flow depends on the number of orders, or the current address, then we must ensure that the control flow is always returned the same answer. This is achieved by storing the result of the first execution, and using that result for every replay (making the control flow deterministic). Now to the idempotency. What if a side effect does complete, but a failure of some kind causes the result to not be stored by the framework? Well, the durable execution framework will replay the function, see no stored result and execute the side effect again. For this reason we want side effects to either be idempotent or otherwise tolerate running more than once. For example, we might decide that sending the same email again is ok. The cost of reliable idempotency might not be worth it. On the other hand, a credit card payment most definitely should be idempotent. Some frameworks make the separation of control flow from side effects explicit, namely, Temporal. In the Temporal programming model, the workflow definition is the control flow and each activity is a side effect (or some sort of non-deterministic operation). Other frameworks such as Resonate and Restate are based on functions which can call other functions which can result in a tree of function calls. Each function in this tree has a portion of control flow and side effects (either executed locally or via a call to another function). Fig 7. A tree of function calls, with control-flow in each function. The same need for determinism in the control flow is needed in each of these functions. This is guaranteed by ensuring the same inputs, and the replacement of non-deterministic operations (such as date/times, random numbers, ids, retrieved objects) with deterministic ones. Our mental model is built on separating a durable function into the control flow and the side effects. Some frameworks actually explicitly separate the two (like Temporal) while others are more focused on composable functions. The need for determinism in control flow is a by-product of recovery being based on retries of the function. If we could magically reach into the function, to the exact line to resume from, reconstructing the local state and executing from there, we wouldn’t need deterministic control flow code. But that isn’t how it works. The function is executed again from the top, and it better make the same decisions again, or else you might end up with weird behaviors, inconsistencies or even double charging your customers. The side effects absolutely can and should be non-deterministic, which is fine because they should generally only be executed once, even if the function itself is executed many times. For those failure cases where the result is not durably stored, we rely on idempotency or duplication tolerance. This is a pretty generalized model. There are a number of nuances and differences across the frameworks. Some of the examples would actually result in a non-determinism error in Temporal, due to how it records event history and expects a matching replay. The developer must learn the peculiarities of each framework. Hopefully this post provides a general overview of determinism in the context of durable execution. Recovery through re-execution. Separation of control flow from side effects. Determinism in control flow Idempotency and duplication tolerance in side effects Step 1, make a db call. Step 2, make an API call. Step 3, send an email.

0 views
Jeff Geerling 1 weeks ago

How to silence the fan on a CM5 after shutdown

Out of the box, if you buy a Raspberry Pi Compute Module 5, install it on the official CM5 IO Board, and install a fan on it (e.g. my current favorite, the EDAtec CM5 Active Cooler ), you'll notice the fan ramps up to 100% speed after you shut down the Pi. That's not fun, since at least for a couple of my CM5s, they are more often powered down than running, creating a slight cacophany!

0 views
The Coder Cafe 1 weeks ago

Build Your Own Key-Value Storage Engine—Week 2

Curious how leading engineers tackle extreme scale challenges with data-intensive applications? Join Monster Scale Summit (free + virtual). It’s hosted by ScyllaDB, the monstrously fast and scalable database. Agenda Week 0: Introduction Week 1: In-Memory Store Week 2: LSM Tree Foundations Before delving into this week’s tasks, it’s important to understand what you will implement. This week, you will implement a basic log-structured merge-tree (LSM tree). At its core, an LSM tree is a data structure that prioritizes write efficiency by trading off some read complexity. It buffers writes in memory and uses append-only files on disk, then rewrites data during compaction. It consists of two main components: A mutable in-memory data structure called a memtable, used to store recent writes. A set of immutable SSTables (Sorted String Table) stored on disk. Regularly, the current memtable is snapshotted, its entries are sorted by key, and a new immutable SSTable file is written. In addition, a MANIFEST file is an append-only list of SSTable filenames. It tells the engine which SSTable files exist and in which order to read them, newest to oldest. Why LSM trees shine for write-heavy workloads: Fast writes with sequential I/O: New updates are buffered in memory (memtable) and later written sequentially to disk during a flush (SSTable), which is faster than the random I/O patterns common with B-trees, for example. Decouples writes from read optimization: Writes complete against the memtable, while compaction work runs later (you will tackle that in a future week). Space and long-term efficiency: Compaction processes remove dead data and merge many small files into larger sorted files, which keeps space usage in check and sustains read performance over time. For the memtable, you will start with a hashtable. In a future week, you will learn why a hashtable is not the most efficient data structure for an LSM tree, but it is a simple starting point. For the SSTables, you will use JSON as the data format. Get comfortable with a JSON parser if you are not already. 💬 If you want to share your progress, discuss solutions, or collaborate with other coders, join the community Discord server ( channel): Join the Discord This week’s implementation is single-threaded. You will revisit that assumption later. Implement a hashtable to store requests (create or update). You can probably reuse a lot of code from Week 1. When your memtable contains 2,000 entries: Flush the memtable as a new immutable JSON SSTable file with keys sorted. The SSTable file is a JSON array of objects, each with two fields, and . Keys are unique within a file. For example, if your memtable contains the following entries: You need to create the following SSTable: Use a counter for the filename prefix, for example , , . After writing the new SSTable, append its filename to the MANIFEST (append only), then clear the memtable: For now, the flush is a stop-the-world operation. While the file is being written, do not serve reads or writes. You will revisit that later. Create an empty file if it doesn’t exist. Derive the next SSTable ID from the MANIFEST so you don't reuse the same filename. Check the memtable: If found, return the corresponding value. If not found, read the MANIFEST to list SSTable filenames: Scan SSTables from newest to oldest (for example , then , then ). Use a simple linear scan inside each file for now. Stop at the first hit and return the corresponding value. If still not found, return . There are no changes to the client you built in week 1. Run it against the same file ( put.txt ) to validate that your changes are correct. Keep a small LRU cache of known-absent keys (negative cache) between the memtable and SSTables. This avoids repeated disk scans for hot misses: after the first miss, subsequent lookups are O(1). Implementation details are up to you. Instead of parsing the MANIFEST file for each request, you can cache the content in-memory. That’s it for this week! You have built the first version of an LSM tree: a memtable in memory, SSTable files written by regular flushes, and a MANIFEST that lists those SSTables. For now, durability isn’t guaranteed. Data already flushed to SSTables will be read after a restart, but anything still in the memtable during a crash is lost. In two weeks, you will make sure that any request acknowledged to a client remains in your storage engine, even after a restart. The flush trigger you used was pretty simple: once the memtable contains 2,000 entries. In real systems, flushes can be triggered by various factors, for example: Some databases flush when the memtable reaches a target size in bytes, ensuring predictable memory usage. A flush can also occur after a period of time has passed. This occurs because the database eventually needs to release commit log segments. For tables with very low write activity, this can sometimes lead to data resurrection scenarios. Here’s an old issue from the ScyllaDB codebase that illustrates this behavior. Regarding the model, this series assumes a simple key–value one: every PUT stores the whole value, so a GET just finds the newest entry and returns it. If you need a richer model (e.g., rows with many fields or collections), writes are often partial (patches) rather than full replacements. Therefore, reads must reconstruct the result by scanning newest to oldest and merging changes until all required fields are found or a full-write record is encountered. Last but not least, in this series, you implicitly rely on client-side ordering: the validation client issues requests sequentially. Production KV databases typically attach a sequence number or a logical timestamp to each write to handle out-of-order arrivals, merging, and reconciling results. Pure wall-clock timestamps are convenient but brittle; see Kyle Kingsbury’s notes on clock pitfalls for a deeper dive. Missing direction in your tech career? At The Coder Cafe, we serve timeless concepts with your coffee to help you master the fundamentals. Written by a Google SWE and trusted by thousands of readers, we support your growth as an engineer, one coffee at a time. The Log-Structured Merge-Tree (LSM-Tree) // The original LSM tree whitepaper. Log Structured Merge Tree - ScyllaDB // LSM tree definition from ScyllaDB technical glossary . ❤️ If you enjoyed this post, please hit the like button. Week 0: Introduction Week 1: In-Memory Store Week 2: LSM Tree Foundations A mutable in-memory data structure called a memtable, used to store recent writes. A set of immutable SSTables (Sorted String Table) stored on disk. Fast writes with sequential I/O: New updates are buffered in memory (memtable) and later written sequentially to disk during a flush (SSTable), which is faster than the random I/O patterns common with B-trees, for example. Decouples writes from read optimization: Writes complete against the memtable, while compaction work runs later (you will tackle that in a future week). Space and long-term efficiency: Compaction processes remove dead data and merge many small files into larger sorted files, which keeps space usage in check and sustains read performance over time. This week’s implementation is single-threaded. You will revisit that assumption later. Flush the memtable as a new immutable JSON SSTable file with keys sorted. The SSTable file is a JSON array of objects, each with two fields, and . Keys are unique within a file. For example, if your memtable contains the following entries: You need to create the following SSTable: Use a counter for the filename prefix, for example , , . After writing the new SSTable, append its filename to the MANIFEST (append only), then clear the memtable: Create an empty file if it doesn’t exist. Derive the next SSTable ID from the MANIFEST so you don't reuse the same filename. Check the memtable: If found, return the corresponding value. If not found, read the MANIFEST to list SSTable filenames: Scan SSTables from newest to oldest (for example , then , then ). Use a simple linear scan inside each file for now. Stop at the first hit and return the corresponding value. If still not found, return . Some databases flush when the memtable reaches a target size in bytes, ensuring predictable memory usage. A flush can also occur after a period of time has passed. This occurs because the database eventually needs to release commit log segments. For tables with very low write activity, this can sometimes lead to data resurrection scenarios. Here’s an old issue from the ScyllaDB codebase that illustrates this behavior. The Log-Structured Merge-Tree (LSM-Tree) // The original LSM tree whitepaper. Log Structured Merge Tree - ScyllaDB // LSM tree definition from ScyllaDB technical glossary .

0 views
iDiallo 1 weeks ago

How Do You Send an Email?

It's been over a year and I didn't receive a single notification email from my web-server. It could either mean that my $6 VPS is amazing and hasn't gone down once this past year. Or it could mean that my health check service has gone down. Well this year, I have received emails from readers to tell me my website was down. So after doing some digging, I discovered that my health checker works just fine, but all emails it sends are being rejected by gmail. Unless you use a third party service, you have little to no chance of sending an email that gets delivered. Every year, email services seem to become a tad bit more expensive. When I first started this website, sending emails to my subscribers was free on Mailchimp. Now it costs $45 a month. On Buttondown, as of this writing, it costs $29 a month. What are they doing that costs so much? It seems like sending emails is impossibly hard, something you can almost never do yourself. You have to rely on established services if you want any guarantee that your email will be delivered. But is it really that complicated? Emails, just like websites, use a basic communication protocol to function. For you to land on this website, your browser somehow communicated with my web server, did some negotiating, and then my server sent HTML data that your browser rendered on the page. But what about email? Is the process any different? The short answer is no. Email and the web work in remarkably similar fashion. Here's the short version: In order to send me an email, your email client takes the email address you provide, connects to my server, does some negotiating, and then my server accepts the email content you intended to send and saves it. My email client will then take that saved content and notify me that I have a new message from you. That's it. That's how email works. So what's the big fuss about? Why are email services charging $45 just to send ~1,500 emails? Why is it so expensive, while I can serve millions of requests a day on my web server for a fraction of the cost? The short answer is spam . But before we get to spam, let's get into the details I've omitted from the examples above. The negotiations. How similar email and web traffic really are? When you type a URL into your browser and hit enter, here's what happens: The entire exchange is direct, simple, and happens in milliseconds. Now let's look at email. The process is similar: Both HTTP and email use DNS to find servers, establish TCP connections, exchange data using text-based protocols, and deliver content to the end user. They're built on the same fundamental internet technologies. So if email is just as simple as serving a website, why does it cost so much more? The answer lies in a problem that both systems share but handle very differently. Unwanted third-party writes. Both web servers and email servers allow outside parties to send them data. Web servers accept form submissions, comments, API requests, and user-generated content. Email servers accept messages from any other email server on the internet. In both cases, this openness creates an opportunity for abuse. Spam isn't unique to email, it's everywhere. My blog used to get around 6,000 spam comments on a daily basis. On the greater internet, you will see spam comments on blogs, spam account registrations, spam API calls, spam form submissions, and yes, spam emails. The main difference is visibility. When spam protection works well, it's invisible. You visit websites every day without realizing that behind the scenes. CAPTCHAs are blocking bot submissions, rate limiters are rejecting suspicious traffic, and content filters are catching spam comments before they're published. You don't get to see the thousands of spam attempts that happen every day on my blog, because of some filtering I've implemented. On a well run web-server, the work is invisible. The same is true for email. A well-run email server silently: There is a massive amount of spam. In fact, spam accounts for roughly 45-50% of all email traffic globally . But when the system works, you simply don't see it. If we can combat spam on the web without charging exorbitant fees, email spam shouldn't be that different. The technical challenges are very similar. Yet a basic web server on a $5/month VPS can handle millions of requests with minimal spam-fighting overhead. Meanwhile, sending 1,500 emails costs $29-45 per month through commercial services. The difference isn't purely technical. It's about reputation, deliverability networks, and the ecosystem that has evolved around email. Email providers have created a cartel-like system where your ability to reach inboxes depends on your server's reputation, which is nearly impossible to establish as a newcomer. They've turned a technical problem (spam) into a business moat. And we're all paying for it. Email isn't inherently more complex or expensive than web hosting. Both the protocols and the infrastructure are similar, and the spam problem exists in both domains. The cost difference is mostly artificial. It's the result of an ecosystem that has consolidated around a few major providers who control deliverability. It doesn't help that Intuit owns Mailchimp now. Understanding this doesn't necessarily change the fact that you'll probably still need to pay for email services if you want reliable delivery. But it should make you question whether that $45 monthly bill is really justified by the technical costs involved. Or whether it's just the price of admission to a gatekept system. DNS Lookup : Your browser asks a DNS server, "What's the IP address for this domain?" The DNS server responds with something like . Connection : Your browser establishes a TCP connection with that IP address on port 80 (HTTP) or port 443 (HTTPS). Request : Your browser sends an HTTP request: "GET /blog-post HTTP/1.1" Response : My web server processes the request and sends back the HTML, CSS, and JavaScript that make up the page. Rendering : Your browser receives this data and renders it on your screen. DNS Lookup : Your email client takes my email address ( ) and asks a DNS server, "What's the mail server for example.com?" The DNS server responds with an MX (Mail Exchange) record pointing to my mail server's address. Connection : Your email client (or your email provider's server) establishes a TCP connection with my mail server on port 25 (SMTP) or port 587 (for authenticated SMTP). Negotiation (SMTP) : Your server says "HELO, I have a message for [email protected]." My server responds: "OK, send it." Transfer : Your server sends the email content, headers, body, attachments, using the Simple Mail Transfer Protocol (SMTP). Storage : My mail server accepts the message and stores it in my mailbox, which can be a simple text file on the server. Retrieval : Later, when I open my email client, it connects to my server using IMAP (port 993) or POP3 (port 110) and asks, "Any new messages?" My server responds with your email, and my client displays it. Checks sender reputation against blacklists Validates SPF, DKIM, and DMARC records Scans message content for spam signatures Filters out malicious attachments Quarantines suspicious senders Both require reputation systems Both need content filtering Both face distributed abuse Both require infrastructure to handle high volume

0 views
Rik Huijzer 1 weeks ago

Generating an SSH key for a webserver

Assuming you have the SSH password for a webserver called say `case` and email `[email protected]`, you can generate a key as follows: ``` ssh-keygen -t rsa -b 4096 -C "[email protected]" -f ~/.ssh/case ``` Next, add the server, which has say username `user` at location `case.example.com`, to your `~/.ssh/config`: ``` Host case HostName case.example.com User user IdentityFile ~/.ssh/case ``` Then you can copy this key to the server ``` ssh-copy-id -i ~/.ssh/case [email protected] ``` and afterwards log in with ``` ssh case ```

0 views
W. Jason Gilmore 1 weeks ago

Toggling Between Fullscreen Editor and Terminal In VSCode and Cursor

November 18, 2025: This article was originally published on June 4, 2024 and later updated for clarity after I returned to it and couldn't figure out which file to modify! I've been a Vim user for decades however a few years ago I switched to VS Code and then subsequently Cursor for my web development work. When building modern web apps you'll spend almost as much time running shell commands as coding, so I need to have a terminal within easy reach at all times. In fact I typically keep several terminal tabs open, including one opened to the local MySQL instance, one running a worker, and one to execute various shell commands including those related to managing my Git repository. I want this transition between editor and terminal to be as seamless as possible and so I setup two keyboard shortcuts to help me quickly move back and forth between the two. Furthermore, the transition will always open the terminal in fullscreen mode so I'm not fighting with screen real estate on a laptop. To configure these shortcuts, open the keyboard shortcuts ( ) file in JSON mode and add the following entries: I've used for the toggling shortcut, however you can switch this to whatever you'd like. If you're running Windows I suppose you would change the shortcut to or something like that. Once defined, save the changes and then try using the keyboard shortcut to switch between the two. With the terminal maximized your VS Code enviroment will look like this: If you have any other VS Code screen optimization tips, I'd love to hear about them! Hit me up on Twitter at @wjgilmore .

0 views
JSLegendDev 1 weeks ago

5 Pixel Art Tips for Programmers

Programmers are known to not have a strong suit for art related disciplines, pixel art is no exception. In this post, I’ll share 5 tips for making good pixel art from the point of view of a programmer. This is to help other solo devs coming from a programming background to make better pixel art for their games. Now, you might be wondering if I’m qualified to share tips related to pixel art? Below are two pixel art asset packs I made. If you like how they look, then the question is answered. https://jslegend.itch.io/kirby-like-platformer-asset-pack https://jslegend.itch.io/samurai-arena Here are all the asset packs I’ve made so far : Kirby like platformer asset pack (FREE) Mario-like Platformer Asset Pack ($4) 2D Pixel Art Samurai Arena ($4) How do you know if a sprite looks good or not? You might have assumed that you just need to look at it and your brain determines whenever it’s appealing or not. Maybe the colors clash or the pixels are not placed symmetrically, etc… all things that you should detect naturally. However, it’s not that simple, an art piece can go against commonly held rules and still look good. It can look good to one person but not to another. While it’s true that whether a sprite or any piece of art looks good is subjective to some extent, it’s undeniable that some of them are widely considered more appealing. This subjective nature of art is what a lot of programmers struggle with. You might be used to having a compiler compile and run your code. If your code successfully runs then you know that at least your code works. With art however, you need to acquire good taste that will effectively act as your own internal compiler when doing pixel art. It will guide you during your art process and allow you to realize when things don’t work (ex: Color clashes, etc…). The question now becomes, how do you develop good taste in pixel art? The simplest way is to look at well regarded pixel art online. It can come from games where the art is praised or from pixel artists online. When observing each piece, try to notice the details. If you do this enough, you’ll build an internal understanding of what makes good pixel art. https://x.com/nekocrocodile/status/1894101890406776904 https://x.com/ChrisPariano/status/1886151340541227153 Tip #2 : Don’t Draw! Negotiate Your Pixels Instead If you come to pixel art with the mindset that you’re going to draw what you want like you would on pen and paper, you’re going to get frustrated. Pixel art at low resolutions is all about negotiating with your canvas. It’s basically a puzzle game. You need to figure out where to place your pixels to represent what you want while still compromising on certain details. The sooner you view pixel art like this, the less you’re going to struggle drawing the sprites you need for your games. This is because you’re now more flexible and ready to adapt when representing certain things that are challenging at low resolutions. You can either do the hard work of figuring out how to represent an object or character in lower resolutions or you can go look at the million of available game sprites that already solved what you’re trying to represent. That’s why I recommend using references extensively. For example, if you want to make a top-down action adventure game a la Zelda there is no shame in looking up link’s sprites to see how he’s represented or how his sword attack animations are made. In case you didn’t know, the most comprehensive website for looking up sprites from various retro games is The Spriters Resources . Tip #4 : Stick to a Limited Color Palette but Expand It if Needed Sticking to a limited color palette is very important in pixel art due to the low amount of space available. Every color you choose will have a big impact on the appeal of the final sprite. If you don’t want to deal with color theory, you can always pick an existing color palette. There is this website called Lospec with plenty of color palettes you can pick from. On this website, you can often see pixel art examples using the listed color palette. This will help you determine if a given palette will likely help you achieve your desired aesthetic. Another option consists in finding a pixel art piece you like online and picking the same colors to make your palette. Finally, you might feel too constrained working with a limited amount of colors. Sometimes, the colors you have in your palette are not enough. In that case, I recommend adding colors to it. In fact, it’s much easier to expand a given color palette one color at a time then to not limit yourself to one by choosing colors on the fly. When needing a new color, you can more easily compare your new color choice with an existing palette and see if it fits or change your choice otherwise. I often hear the phrase “Constraints breeds creativity” and I agree because when constrained you often end up finding creative solutions. However, another lesser known benefit of constraints is that they allow you to hide your flaws. The less constraints you have, the more ambitious you get, the higher the likelihood of going out of your comfort zone and finding yourself in an area where you do not have enough skill to pass the “professional” quality bar. For example, if you’re someone with very limited coloring skills. I would recommend sticking to a prefined color palette of max 4 colors. This way, you can more easily reach a “professional” looking result compared to if you had to pick your own colors. Well chosen constraints allows you to put forward your strengths while hiding your flaws . Another way to put it would be that constraints reduces the likelihood of shooting yourself in the foot. While I hope these tips will help you make better pixel art, it’s no secret that practice remains a big part of what you need to do to achieve good results. If you’re interested in reading more posts like this. I recommend subscribing to my Substack as to not miss out on future releases. Subscribe now In the meantime, you can check out my previous posts. Programmers are known to not have a strong suit for art related disciplines, pixel art is no exception. In this post, I’ll share 5 tips for making good pixel art from the point of view of a programmer. This is to help other solo devs coming from a programming background to make better pixel art for their games. Now, you might be wondering if I’m qualified to share tips related to pixel art? Below are two pixel art asset packs I made. If you like how they look, then the question is answered. https://jslegend.itch.io/kirby-like-platformer-asset-pack https://jslegend.itch.io/samurai-arena Here are all the asset packs I’ve made so far : Kirby like platformer asset pack (FREE) Mario-like Platformer Asset Pack ($4) 2D Pixel Art Samurai Arena ($4)

0 views
Michael Hoffmann 1 weeks ago

Navigating State Management in Vue: Composables, Provide/Inject, and Pinia

Explore the best practices for using Composables, Provide/Inject, and Pinia in Vue applications. Learn when to use each approach to manage state effectively in your projects, understanding the nuances that make each method suitable for different scenarios.

0 views
(think) 2 weeks ago

Rust: Embrace Captured Identifier in Format Strings

While playing with Rust recently I’ve noticed that most Rust tutorials suggest writing code like this: Clippy (Rust’s mighty linter), however, doesn’t like it and suggests doing this instead: I hope you’ll agree that this looks cleaner. It certainly seems that way to me. At this point you might be wondering what’s the point of this article. Well, I thought I had come across some recent Rust feature and this was the reason why it wasn’t adopted much in the wild. Turns out, however, that this was introduced almost 4 years ago in Rust 1.58 . I think there are two reasons why the new style hasn’t taken off: If you ask something like ChatGPT how to print variables in Rust you’ll get mostly answers like: LLMs are great, but one certainly has to be aware of their limitations. You also have to be aware of the limitations of the updated syntax - most importantly it works only for variable names, as opposed to arbitrary Rust expressions: Perhaps future versions of Rust will address this limitation. Time will tell. So, that’s all from me. Let’s hope some LLMs will pick up on it and suggest to more people the “new” and improved syntax for captured identifiers in format strings. Keep hacking! There are many outdated tutorials out there LLMs were trained on large samples of code using the old style and they naturally favor it

0 views
pabloecortez 2 weeks ago

You can read the web seasonally

What if you read things around the web the way you watch movies or listen to music? A couple of days ago I made a post on Mastodon introducing lettrss.com, a project I made that takes a book in the public domain and sends one chapter a day to your RSS reader. Xinit replied with a great point about RSS feed management: This is fascinating, but I know how it would go based on the thousands of unread RSS feeds I've had, and the thousands of unheard podcasts I subscribed to. I'd end up with an RSS of unread chapters, representing a whole book in short order. Regardless of my inability to deal, it remains a great idea, and I will absolutely recommend while hiding my shame of a non-zero inbox. When I first started using RSS, I thought I'd found this great tool for keeping tabs on news, current events, and stuff I should and do care about. After adding newspapers, blogs, magazines, publications, YouTube channels and release notes from software I use, I felt a false sense of accomplishment, like I'd finally been able to wrangle the craziness of the internet into a single app, like I had rebelled against the algorithm™️. But it didn't take long to accumulate hundreds of posts, most of which I had no true desire to read, and soon after I abandoned my RSS reader. I came back to check on it from time to time, but its dreadful little indicator of unread posts felt like a personal failure, so eventually I deleted it entirely. Will Hopkins wrote a great post on this exact feeling. I don't actually like to read later : I used Instapaper back in the day, quite heavily. I built up a massive backlog of items that I'd read occasionally on my OG iPod Touch. At some point, I fell off the wagon, and Instapaper fell by the wayside. [...] The same thing has happened with todo apps over the years, and feed readers. They become graveyards of good intentions and self-imposed obligations. Each item is a snapshot in time of my aspirations for myself, but they don't comport to the reality of who I am. I couldn't have said it better myself. This only happens with long-form writing, whenever I come across an essay or blog post that I know will either require my full attention or a bit more time than I'm willing to give it in the moment. I've never had that issue with music. Music is more discrete. It's got a timestamp. I listen to music through moods and seasons, so much so that I make a playlist for every month of the year like a musical scrapbook. What if we took this approach to RSS feeds? Here's what I replied to Xinit: This is something I find myself struggling with too. I think I'm okay knowing some RSS feeds are seasonal, same as music genres throughout the year. Some days I want rock, others I want jazz. Similarly with RSS feeds, I've become comfortable archiving and resurfacing feeds. For reference, I follow around 10 feeds at any given time, and the feeds I follow on my phone are different from the ones on my desktop. You shouldn't feel guilty about removing feeds from your RSS readers. It's not a personal failure, it's an allocation of resources like time and attention.

0 views
(think) 2 weeks ago

Configure Quick Terminal Size in Ghostty

I’m a very heavy quick (dropdown) terminal user, so after adopting Ghostty one of the points of frustration for me was that I could not specify a default size for my quick terminal. Instead I had to adjust the size every time I started Ghostty. Fortunately, the problem recently got solved with the introduction of the config option in Ghostty 1.2. Its behavior is linked to that of ( by default). You can configure the default size for both the primary axis (e.g. height) and the secondary axis (e.g. width) in either a percentage or pixels. I like my dropdown terminal to be relatively big, so I just do the following: But, you can do a lot more, as indicated by the docs. If you specify two values, then the second value is the secondary axis. The examples below illustrate a few things you can do: As mentioned earlier, the primary axis is defined by the configuration. For the and values, the primary axis is the height. For the and values, the primary axis is the width. For , it depends on your monitor orientation: it is height for landscape and width for portrait. You’ve got many options, but I doubt many people will need those. That’s all I have for you today. Keep hacking!

0 views
Stavros' Stuff 2 weeks ago

I converted a rotary phone into a meeting handset

As you may remember, or completely not know, I have a bit of a fascination with old rotary phones . Occasionally, when people learn about this fascination, they donate their old rotary phones to me, so I have ended up with a small collection. The other thing I have a fascination with is meetings. Well, I say “fascination”, but it’s more of a burning hatred, really. One day, a few months ago, I was in one such meeting, as I have been every day since, and I jokingly pretended to get irate about something. One of my coworkers laughed and said “I bet if this were a phone call, you’d slam the phone down right now”, and a dread spread over me. Why didn’t I have a phone handset I could slam down? Had I really become a corporate husk of my former, carefree self, puppeteered by the vicissitudes of capitalism? “No”, I decided, “because that sentence doesn’t even make sense”. I did, however, have a phone I could use for this project, 30% of the knowledge required, and 100% of the underestimation of how hard the other 70% would be. Armed with all these numbers, I quickly started to try to figure out how I could do this. The phone I used is an old Siemens rotary phone, pictured in the image to the right. That image is actually not a photo of the phone, but ChatGPT’s best attempt at one, because I’m too lazy to try to find where I put the phone to take a photo of it. Rest assured, though, the image is almost exactly what the phone looks like, except with a bit more 8 and a bit less 3. The good thing about these old phones is that nothing is soldered to anything else, which makes it possible to modify them without making any permanent changes to the phone, something which I really wanted to avoid. I don’t really think it matters much, but I don’t like breaking/altering these old phones at all. I prefer to make reversible changes where I can, and luckily the phones allow that. The phone’s board has these metal tabs that stick out, and the cables have a corresponding connector that opens up around the tab, making decent electrical contact and using friction to make sure they don’t slip off. Since I didn’t want to make any permanent changes to the phone, I didn’t want to remove these tabs, or to solder anything onto them. I just wanted to connect a cable to them in the easiest way possible. To do that, I designed and 3D-printed a very small connector (pictured in the photo on the right, the small, purple piece of plastic), which I used to hold a wire on top of the tab. This worked fairly well, the cable made good contact, and was relatively stable, as long as you didn’t pull on it at all. With the connections out of the way, I could move on to the electronics themselves, and where they’d go. Another benefit of these old phones are that they’re much larger than the circuits inside them, which means that the interior is very, very roomy, with lots of space for all the extra bits I wanted to use. Since I wanted to be able to use the phone as a meeting handset, I figured I needed something that would act as a keyboard/soundcard combo. The keyboard would be responsible for actually “hanging up” the meeting, ie sending the appropriate keystrokes to the active window to exit whatever meeting I happened to be on. The soundcard would expose a microphone/speaker combo, which could be used as an input and output device for the meeting software to play and record sound through the phone’s handset. I figured, since I’m at it, I might as well make the rotary dial work too, and have the keyboard type whatever number I dialed, just because I could. To do all this, I needed a capable microcontroller, and I had just the thing: The RP2040 by Raspberry Pi is plenty powerful enough to be used as a sound card, and can be made to show up to the computer as a USB device. Of course, if the RP2040 was to act as a USB device that’s a combination sound card/keyboard, I’d have to spend a lot of time learning about USB devices, sound cards, keyboards, and how the RP2040 works, which is a prospect which I relished with considerable gusto, said no one ever, and especially not me. Instead, we have LLMs now, and we can make them do the dirty work we don’t want to do, like program stuff for our inane side-projects. This is literally why LLMs were created and nobody can convince me otherwise, so I decided to help Claude Opus 4.1 (the best coding model at the time) progress on its goal towards self-actualization, and got to work. Unfortunately, Opus was only available on the $200/mo subscription, which was a bit too much for a silly side-project, so I decided to use the API instead for a few hours of coding. I mean, it’s one LLM, Michael. What could it cost? Ten dollars? I asked Claude to write some code to turn the RP2040 into a sound card using TinyUSB, I tested it and told Claude about the way in which it didn’t work, it wrote more code, and so on. Half an hour and fifty dollars later, I realized I had spent fifty dollars on this, and that this was not sustainable because, if anything, the code was getting more and more buggy the more Claude fixed it. It was time for plan B. Plan B is shameful, as it contains an element of me accepting defeat, but I guess it’s actually Claude that was defeated. Be that as it may, I decided that the RP2040 sound card approach was a dead-end, as I didn’t know anything about the RP2040 or about sound cards, and that I’d have to change my tactics. I’d use a USB hub with two separate devices, one sound card and one keyboard, and the hub would join them and allow them to use a single USB cable to connect to the host computer. I could still use the RP2040 as a keyboard, so I connected it to the phone’s hook and rotary dial, and wrote some code to measure the pulses and send keystrokes if the handset was placed on the hook. After verifying that this worked properly, I moved on to the second, and by far the hardest, part of my plan, finding an off-the-shelf sound card. I ran to my trusty shopping website, Amazon (the AliExpress of the US), but all the USB sound cards there were a bit more expensive than they needed to be, so then I went straight to the source, AliExpress. There, I found exactly what I needed: A USB sound card for $1.69, and the sexual reference was not lost on me. Well done, AliExpress. When the sound card arrived, I tested it on my computer, saw that it worked fine, and disassembled it. I removed the two 3.5mm jack connectors and soldered pins to them instead, with the intention that the phone’s connectors would slide over the pins instead of the metal tabs of the phone. Indeed, this worked beautifully, and the handset made a very solid connection with the sound card. I plugged the latter into my computer and confirmed that I could both listen to and record from the handset. I then desoldered the USB connector and soldered four wires onto where it used to be, to save space. I soldered the other side of those wires to the hub, where I desoldered the corresponding connector from, and tested to see if this worked. Amazingly, it did! The computer recognized the sound card, and audio worked fine with the handset. I connected the RP2040 keyboard to the hub as well, and confirmed that that, too, worked well, sending various keystrokes when hung up (Ctrl+Shift+E for Zoom, by Ctrl+W for Meet, for Teams, etc). To clarify, the RP2040 doesn’t actually know which software you’re using for the call, it just sends all the keystrokes, one after the other. On the image to the right, you can see everything connected together. The red square to the right of the phone is the USB hub, with wires coming out of it and going to the sound card, which has been connected to the handset. On the other end, a short USB cable leads to the RP2040, which has been connected to the hook and rotary dial. It took a bit of trial and error to find the hook connectors, but nothing too terrible. The hook is a simple switch, so the detection happens with a GPIO pin that gets pulled low whenever the phone is hung up. The rotary dial is similarly a second switch, one that opens and closes very quickly, a number of times equal to the number you just dialed. The software on the RP2040 just counts these opens and closes, waits a few milliseconds to see if there are any more of them, and, if not, simulates a keyboard typing the number it counted. Here’s a video of the whole thing, including the hanging up money shot: I hope this post made sense, it was a bit stream-of-consciousness but this was a pretty simple build, with nothing really too involved or complicated. The most complicated part was probably the RP2040 keyboard emulation, and even that was pretty simple, because the LLM did it on its own. If you have any feedback, questions, or hate mail, you can find me on Bluesky , or email me directly.

0 views
Pat Shaughnessy 2 weeks ago

YARV’s Internal Stack and Your Ruby Stack

I've started working on a new edition of Ruby Under a Microscope that covers Ruby 3.x. I'm working on this in my spare time, so it will take a while. Leave a comment or drop me a line and I'll email you when it's finished. The content of Chapter 3, about the YARV virtual machine, hasn't changed much since 2014. However, I did update all of the diagrams to account for some new values YARV now saves inside of each stack frame. And some of the common YARV instructions were renamed as well. I also moved some content that was previously part of Chapter 4 here into Chapter 3. Right now I'm rewriting Chapter 4 from scratch, describing Ruby's new JIT compilers. As we’ll see in a moment, YARV uses a stack internally to track intermediate values, arguments, and return values. YARV is a stack-oriented virtual machine. In addition to its own internal stack, YARV keeps track of your Ruby program’s call stack , recording which methods call which other methods, functions, blocks, lambdas, and so on. In fact, YARV is not just a stack machine—it’s a double-stack machine! It has to track the arguments and return values not only for its own internal instructions but also for your Ruby program. Figure 3-1 shows YARV’s basic registers and internal stack. YARV’s internal stack is on the left. The SP label is the stack pointer, or the location of the top of the stack. On the right are the instructions that YARV is executing. PC is the program counter, or the location of the current instruction. You can see the YARV instructions that Ruby compiled from the puts 2+2 example on the right side of Figure 3-1. YARV stores both the SP and PC registers in a C structure called rb_control_frame_t , along with the current value of Ruby’s self variable and some other values not shown here. At the same time, YARV maintains another stack of these rb_control_frame_t structures, as shown in Figure 3-2. This second stack of rb_control_frame_t structures represents the path that YARV has taken through your Ruby program, and YARV’s current location. In other words, this is your Ruby call stack—what you would see if you ran puts caller . The CFP pointer indicates the current frame pointer. Each stack frame in your Ruby program stack contains, in turn, a different value for the self, PC, and SP registers, as shown in Figure 3-1. Ruby also keeps track of type of code running at each level in your Ruby call stack, indicated by the “[BLOCK]”, “[METHOD]” notation in Figure 3-2. In order to help you understand this a bit better, here are a couple of examples. I’ll begin with the simple 2+2 example from Chapters 1 and 2, shown again in Listing 3-1. This one-line Ruby script doesn’t have a Ruby call stack, so I’ll focus on the internal YARV stack for now. Figure 3-3 shows how YARV will execute this script, beginning with the first instruction, putself . As you can see in Figure 3-3, YARV starts the program counter (PC) at the first instruction, and initially the stack is empty. Now YARV executes the putself instruction, and pushes the current value of self onto the stack, as shown in Figure 3-4. Because this simple script contains no Ruby objects or classes, the self pointer is set to the default top self object. This is an instance of the Object class that Ruby automatically creates when YARV starts. It serves as the receiver for method calls and the container for instance variables in the top-level scope. The top self object contains a single, predefined to_s method, which returns the string “main.” You can call this method by running the following command in the console: YARV will use this self value on the stack when it executes the opt_send_without_block instruction: self is the receiver of the puts method because I didn’t specify a receiver for this method call. Next, YARV executes putobject 2 . It pushes the numeric value 2 onto the stack and increments the PC again, as shown in Figure 3-5. This is the first step of the receiver (arguments) operation pattern described in “How Ruby Compiles a Simple Script” on page 34. First, Ruby pushes the receiver onto the internal YARV stack. In this example, the Fixnum object 2 is the receiver of the message/method + , which takes a single argument, also a 2. Next, Ruby pushes the argument 2, as shown in Figure 3-6. Finally, Ruby executes the + operation. In this case, opt_plus is an optimized instruction that will add two values: the receiver and the argument, as shown in Figure 3-7. As you can see in Figure 3-7, the opt_plus instruction leaves the result, 4, at the top of the stack. Now Ruby is perfectly positioned to execute the puts function call: The receiver self is first on the stack, and the single argument, 4, is at the top of the stack. (I’ll describe how method lookup works in Chapter 6.) Next, Figure 3-8 shows what happens when Ruby executes the puts method call. As you can see, the opt_send_without_block instruction leaves the return value, nil , at the top of the stack. Finally, Ruby executes the last instruction, leave , which finishes the execution of our simple, one-line Ruby program. Of course, when Ruby executes the puts call, the C code implementing the puts function will actually display the value 4 in the console output.

0 views