Header logo.
small hallucinations
homeyearstagsaboutrss

I Just Wanted to Run :observer on My Mac

tl;dr

On a Mac, if you want to use the Observer GUI for Elixir and Erlang, just use Homebrew's Erlang distribution. It comes with wxWidgets properly configured.

Erlang distributions installed through asdf do not support Observer out of the box. If you install wxWidgets separately through Homebrew, Erlang will complain that wxWidgets was not compiled with the --enable-compat30 flag and is therefore incompatible. (Do not even think about compiling it from source on your own.)

context

Elixir and Erlang ship with a graphical Observer tool that shows how processes interact. This seems very helpful when you have a complex mix of supervisors, GenServers, and processes.

For some reason, I couldn't use it on my Mac. The error message mentioned something about wx, so I looked into it and found this blog post. Following its instructions, I tried installing wxWidgets with Homebrew. The installation failed because a few dependencies were built for x86_64 instead of arm64.

It took me some time to realize that, for some reason, my Homebrew installation targeted x86_64. I don't recall how that happened, but it explained the architecture mismatches I remembered occasionally encountering when adding dependencies. I also had a version of Elixir installed through MacPorts. I removed both MacPorts and Homebrew, then reinstalled Homebrew for the correct architecture.

With the right version of Homebrew in place, I installed wxWidgets again, carefully set environment variables including KERL_CONFIGURE_OPTIONS="--with-wx", and tried to install Erlang and Elixir with the asdf version manager. Erlang failed to install, displaying a message like: “wxWidgets was not compiled with --enable-compat30, wx will NOT be usable.”

So I tried building wxWidgets from source. ChatGPT suggested all the steps: clone the repo and configure it with a few flags, notably --enable-compat30, exactly as the previous error message had indicated.

The configuration script then detected missing submodules, which I downloaded by following its instructions. I repeated this step several times for a handful of missing submodules.

When I finally got the compilation working, I quickly encountered another error: fp.h, required by the built-in libpng, was missing. ChatGPT told me to install libpng through Homebrew and pass a parameter to the configuration script. I did so and reran the script. The same thing then happened with libtiff.

As I was about to give up, I said, “Argh. I just wanted to use :observer.”

At that very moment, ChatGPT finally mentioned the obvious solution, as if it were an afterthought:

Yeah, this rabbit hole is brutal. If your real goal is just :observer, you don’t actually need to hand-build wxWidgets at all. ... Use Homebrew’s Erlang (it comes with the GUI bits working on macOS).

Bruh...

Why didn't you tell me this before I attempted to compile C++ on my own?

Run migrations while deploying a Phoenix app on Railway

I built a small app with Phoenix LiveView and deployed it to Railway. I encountered a few minor roadblocks. Here's how I handled them. Hopefully, this will help someone else.

Deployment

  1. Create a project from my GitHub repository. At this stage, Railway would decide it's an Elixir project and automatically configured deployment workflow.

  2. Right-click on the Railway project canvas, then select “Database” and choose “Add PostgreSQL.”

  3. Set up environment variables as instructed in this section of the documentation. This section lists SECRET_KEY_BASE, LANG, LC_CTYPE, DATABASE_URL, and ECTO_IPV6.

  • Interestingly, I've set LANG and LC_CTYPE to en_US.UTF-8. But I'm still seeing this error: LC_ALL: cannot change locale (en_US.UTF-8). It seems harmless for now.
  • This list also seems incomplete. To make a Phoenix LiveView app work, you need to add the following variables: PHX_SERVER and PHX_HOST. (You can also check runtime.exs for these settings.)
    • Set PHX_SERVER to true.
    • Set PHX_HOST to my-app.up.railway.app. (I'll use my-app as a placeholder name.)
    • If you don't set PHX_SERVER, you'll see this error message in the logs: Configuration :server was not enabled for HaveYourBackWeb.Endpoint, http/https services won't start.
    • If you don't set PHX_HOST correctly, incoming WebSocket requests will be rejected.

Migration

After completing the steps above, the Phoenix app was running and successfully connected to the hosted PostgreSQL database. However, the database remained empty. It turned out that no part of the build or deployment process explicitly ran the migrations.

After some trial and error, I got the migrations to run by following these steps:

  1. In your codebase, create a file at lib/my_app/release.ex with a MyApp.Release module, and define a function to run migrations:
 1defmodule MyApp.Release do
 2  @app :my_app
 3
 4  def migrate do
 5    Application.load(@app)
 6    for repo <- Application.fetch_env!(@app, :ecto_repos) do
 7      {:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
 8    end
 9  end
10end
  1. In lib/my_app/application.ex, add a conditional to run migrations in production.
 1defmodule MyApp.Application do
 2  use Application
 3
 4  @impl true
 5  def start(_type, _args) do
 6
 7    # BEGIN ADDED
 8    if Application.get_env(:my_app, :sql_sandbox) == false do
 9      MyApp.Release.migrate()
10    end
11    # END ADDED
12
13    # Existing code.
14  end
  1. Then, in mix.exs, add the following configuration:
 1defmodule HaveYourBack.MixProject do
 2  use Mix.Project
 3
 4  def project do
 5    [
 6      app: :my_app,
 7      # Existing code.
 8      deps: deps(),
 9
10      # BEGIN ADDED
11      releases: [
12        my_app: [
13          include_executables_for: [:unix],
14          applications: [runtime_tools: :permanent]
15        ]
16      ]
17      # END ADDED
18    ]
19  end
20
21  # Existing code.
  1. Finally, add this command to the Custom Start Command field under Settings -> Deploy.
/app/_build/prod/rel/my-app/bin/my-app eval "MyApp.Release.migrate" && \
/app/_build/prod/rel/my-app/bin/my-app start

When an Azure Health Probe Turns File Uploads into 503s

I was recently working with a container app deployed on Azure. The app provides a REST endpoint that allows users to upload files for processing.

A few days ago, uploads of larger files started failing repeatedly, even though the files weren't particularly large. One that consistently failed to upload was only 4 MB.

I tried uploading the file both with curl and through the web UI. Both attempts failed with HTTP status 503. That ruled out a CORS issue, which would have produced a different status code and would not have caused the curl request to fail.

Interestingly, we couldn't find these POST requests in the logs, suggesting that they never reached the container app.

While inspecting the configuration of this app and others deployed on Azure, I noticed that this one had a health-probe setting.

It turned out that Azure was checking whether the service was alive every ten seconds. Because the app became temporarily unresponsive during uploads and processing, the health probe likely timed out.

Azure would have interpreted this as a sign that the container was down and either removed it from the load balancer or tried to restart it. Either way, the request was abruptly terminated, resulting in a 503 error.

Sell something useless

Apparently, Labubu—the cute plushie with a wicked smile—has become a thing.

Wang Ning, the founder of Pop Mart, believes in selling what is “useless.” One thought experiment he uses to illustrate this idea goes like this:

Would Molly toys sell as well if we added a USB flash drive to them?

That would certainly give the toys some kind of “use,” but it would also remind potential buyers that they do not actually need it. Who really needs another USB thingy, after all?

A similar idea is circulating in Japan's retail industry, where businesses are said to be transitioning from selling “mono” (もの) to selling “koto” (こと). Both words can be translated as “things” in English. The distinction is that “mono” means a tangible object, while “koto” means something intangible, such as an event or experience.

So I wrote a new static-site generator

I used to generate this blog from Markdown files using a static-site generator that I wrote in Python. The resulting HTML files were then hosted on Cloudflare Pages.

Late last year, the Python codebase stopped running smoothly because of dependency issues. I don't remember changing my system or Python version, or adding or removing any of the affected libraries.

First, the Markdown parser I used went missing, so I had to reinstall it in a new virtual environment. Then the feed generator complained about a missing function argument. (Minor problems like these inevitably crop up.)

So I decided to rewrite the SSG behind this blog in Go.

The whole process was pleasant, and GitHub Copilot was very helpful. It suggested dependencies for parsing Markdown and generating RSS feeds—goldmark and gorilla/feeds—as well as often-useful code completions. A few of its solutions used deprecated function calls but were educational nonetheless.

When you program, you carry over old habits from other languages. Here are a few things I learned with nudges from Copilot:

  1. Go doesn't support string interpolation with variable names, and there is a whole discussion about why.
  2. Go doesn't support optional arguments in function signatures. Instead, you can use a variadic function or an options struct.
  3. I tried to import a struct from the main package into a child package. That was a dumb idea because “importing the main package directly can lead to circular dependencies, which are not allowed in Go.” Instead, I could “move the BlogSetting type to a separate package, which can then be imported by both the main and template packages.”

A Tiny Copy-and-Paste UX Paper Cut in Ubuntu

On a Mac, if you press Command+C without selecting anything, nothing happens. I didn't realize that someone had deliberately designed and implemented this behavior until I did the same thing on Ubuntu.

On Ubuntu, pressing Ctrl+C without selecting anything does two things:

  • "\n" is copied to your clipboard; and
  • whatever that has been in your clipboard is overwritten.

I'm talking as if this were an Ubuntu problem, but I don't really know. According to a Reddit comment, it is most likely handled by GNOME together with—in my case—X11.

You would think the clipboard would be a solved problem, given that it was invented in 1973.

Something clever in the syntax of Elixir

I find this aspect of Elixir's syntax quite clever.

There are two sets of Boolean operators in Elixir: and, or, and not, and &&, ||, and !.

The difference is that and, or, and not accept only literal Boolean values. In contrast, &&, ||, and ! can accept values of any type, and all values except false and nil evaluate to true.

When you think about it, this makes a lot of sense. Literal Boolean values—true and false—are words, so it's only natural that they go with the words and, or, and not.