InheritableThreadLocal and Bugs

I was at Devoxx last week, where I enjoyed several talks about the new and upcoming Concurrency features in Java. Maybe I’ll do an overview of those in a future post, but if I’m being honest, they’re already pretty well described elsewhere.

In one of the talks, the speaker mentioned the venerable InheritableThreadLocal, which reminded me of an interesting bug I’d recently encountered, which I’ll share here for posterity.


Death of the User Agent

Once upon a time, web browsers were known as User Agents. The idea presumably being that they are what enables the user to interact with the web. The term “browser” never sat right with me. It turns active agency into passive browsing. Might as well rename the smartphone to scroller.


Grokking Simplicity, part 1

I was recently reminded of Manning’s Grokking series in a discussion on Hacker News. So I figured I’d pick up a copy of Grokking Simplicity, by Eric Normand. The name frustratingly hides the fact that it’s about Functional Programming. I took a bunch of notes while reading it, and I figured I’d clean them up a bit and publish them as a blog post.

This was a pretty interesting book overall. I’ve been programming for over 25 years, mostly in Imperative and Object-oriented styles. I’ve used a bunch of Functional Programming techniques over the years, moreso when Java started to embrace them, but the book still had plenty to teach. At a little over 500 pages, it’s a thorough introduction, with excellent explanations and a gradual introduction of new concepts. This is not a review, but my one point of criticism is that the final chapters (about Functional Architectures) are rather underdeveloped compared to the earlier chapters. These chapters really should be part of a more advanced book on Functional Architecture, and don’t belong in an introductory text like this.

As my notes on this book turned out to be rather extensive (some 3000 words unedited), I’ll split this into multiple blog posts. Consider this to be Part 1, which deals with Stratified Design. There will be a Part 2 which covers Abstractions.


LinkedIn's AI cesspool

I’ve long had a love-hate relationship with LinkedIn. On the one hand, it’s landed me a couple of interesting jobs and lets me keep in touch with people I’ve enjoyed working with. On the other hand, it’s filled with recruiters who make it look like a hive of scum and villainy. Of late, those recruiters have begun resorting to GPTs and other “AI” tools.

Shitty recruiters of yore would rely on blind keyword matching and spam you accordingly. Typically with copy/pasted messages, sometimes with the previous recipient’s name still in the greeting. Always without any sense of clue or decency.

Good recruiters, of course, actually bother to read your profile instead of relying on half-arsed tools. They’re also much more likely to cultivate a working relationship with you instead of merely pushing the same job to 1000 random candidates. They’re not who this rant is about.

It seems like many shitty recruiters have started to use GPT generated messages, probably based on GPT generated summaries of profiles and GPT generated job ads. I didn’t think it possible, but this has actually led to even more useless messages and job offers. Apparently the shitty AI recruiter is as intelligent as the in-the-flesh shitty recruiter.

LinkedIn urgently needs a “block generated messages”-button. Though I would also still like a “block everyone from this company”-button, as shitty recruiters tend to congregate at shitty recruitment agencies.

Thus the enshittification continues.


Loving Obsidian with Excalidraw

I’ve been a note-taker for a long time. It all started with a research journal at work, using pen and paper. As time progressed, I tried various tools and digital approaches, from plain text notes in vim to tools like Zettlr and Joplin.

Eventually, I settled on Obsidian. At its core it’s a nicely polished markdown editor on steroids. One of the things that sets it apart from similar tools (there are at least 55, according to AlternativeTo) is its vibrant plugin community. There are small quality of life plugins such as a simple emoji picker, or tools to refactor your notes.

But there are also utterly amazing tools like Excalidraw. This basically integrates the excalidraw.com drawing tool directly into your notes. But in a way that goes beyond “draw a picture, paste it in a note” type thing. The plugin’s creator, Zsolt Viczián, is an absolute genius. You should totally check out the stuff he makes. I found his “Book on a page” series to be particularly enlightening. He basically draws notes in Excalidraw inside Obsidian which contain text and links to other notes. They look amazing, and they serve as visual book summaries. They also work really well as maps of content.


An ActiveMQ gotcha

Spent way too much time hunting down a strange ActiveMQ issue recently. Finding the cause was complicated by several factors:

  1. The ActiveMQ servers are managed by an external party
  2. Our application runs on Kubernetes, also managed by an external party
  3. The problem only occurred on an environment that’s in use by a customer, not in any of our test environments or during development
  4. We connect to the ActiveMQ servers in several different ways, from various Spring Boot applications, sometimes using annotation magic/garbage and Spring Data connection strings, other times using connection factories in Java
  5. Strangely, the problem only occurred in 1 of our microservices, not in any of the others
  6. Firewall rules make it very hard to talk to the ActiveMQ servers from a local machine

Whats the problem?

Application logs were littered with this ominous exception. Without any further context or details.

java.lang.Exception: Failed to find a store at /path/to/broker.ks
  at org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport.validateStoreURL(SSLSupport.java:399)
  at org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport.loadKeystore(SSLSupport.java:339)
  at org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport.loadKeyManagerFactory(SSLSupport.java:375)
  at org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport.loadKeyManagers(SSLSupport.java:355)
  at org.apache.activemq.artemis.core.remoting.impl.ssl.SSLSupport.createContext(SSLSupport.java:222)
  at org.apache.activemq.artemis.core.remoting.impl.ssl.DefaultSSLContextFactory.getSSLContext(DefaultSSLContextFactory.java:50)
  at org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector.loadJdkSslEngine(NettyConnector.java:786)
  at org.apache.activemq.artemis.core.remoting.impl.netty.NettyConnector$1.initChannel(NettyConnector.java:690)
  at io.netty.channel.ChannelInitializer.initChannel(ChannelInitializer.java:129)
  at io.netty.channel.ChannelInitializer.handlerAdded(ChannelInitializer.java:112)
  at io.netty.channel.AbstractChannelHandlerContext.callHandlerAdded(AbstractChannelHandlerContext.java:1114)
  at io.netty.channel.DefaultChannelPipeline.callHandlerAdded0(DefaultChannelPipeline.java:609)
  at io.netty.channel.DefaultChannelPipeline.access$100(DefaultChannelPipeline.java:46)
  at io.netty.channel.DefaultChannelPipeline$PendingHandlerAddedTask.execute(DefaultChannelPipeline.java:1463)
  at io.netty.channel.DefaultChannelPipeline.callHandlerAddedForAllHandlers(DefaultChannelPipeline.java:1115)
  at io.netty.channel.DefaultChannelPipeline.invokeHandlerAddedIfNeeded(DefaultChannelPipeline.java:650)
  at io.netty.channel.AbstractChannel$AbstractUnsafe.register0(AbstractChannel.java:514)
  at io.netty.channel.AbstractChannel$AbstractUnsafe.access$200(AbstractChannel.java:429)
  at io.netty.channel.AbstractChannel$AbstractUnsafe$1.run(AbstractChannel.java:486)
  at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174)
  at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167)
  at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
  at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:416)
  at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
  at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
  at org.apache.activemq.artemis.utils.ActiveMQThreadFactory$1.run(ActiveMQThreadFactory.java:118)

An odd anniversary

A little over a year ago, I was fired from my job. I haven’t really talked about this in public, but in order to celebrate this dubious anniversary, I figured I’d write a blog post about it. Dates and other specifics have been altered slightly, to make it a bit harder to identify the company and people in question.

Back in 2012, I joined a tiny startup. And when I say tiny, I do mean tiny. Two founders (one an engineer/CTO, the other a salesman/CEO), and one part time freelance engineer. When I joined, they had built a prototype, and had managed to attract a first customer. I was asked to help turn the prototype into a product. This was a boatload of fun, even if it was a bit hectic. After all, Mister Sales had over-promised, and we couldn’t really under-deliver to our first customer. This, for better and for worse, became a running theme.


Introducing RSSDown

I’m an avid user of Markdown related tools. This blog is written in Markdown. My notes are in Markdown (using Obsidian). I often archive web pages as Markdown. The benefits are many: it’s plain text, easily searchable, easy to read on any kind of device, there’s great tooling out there, and it’s flexible enough to fit most needs.

It is for these reasons that I spent some time creating a tool, RSSDown, which takes an RSS or ATOM feed as input, and outputs Markdown. This makes it easy to read blog posts on the command line. It’s also able to persist the output as Markdown files, which can be useful for archiving or synchronization purposes.

The word “tool” is probably a bit generous here. It’s basically a combination of Flexmark, for Markdown conversion, and Rome, for feed parsing.


In praise of modern infra tooling

I’ve been managing servers in one capacity or another since 1999 or so. A lot has changed since then. Many changes have been gradual improvements. Hardware slowly got cheaper, colo slowly got cheaper, bandwidth got a lot cheaper really quickly, partially managed servers and VPSes have become dirt cheap.

With all those improvements on the hardware side of infra, it’s easy to overlook the improvements on the software side. Thanks to tools like Ansible and Let’s Encrypt, I can now deploy an HTTPS site on new hardware in minutes. Where most of those minutes is spent downloading software updates and rebooting into a new kernel. This may sound silly and trivial in 2022. 20 years ago, this would have taken DAYS, and would have involved any number of manual steps. Not only would it have cost me a lot more time, it would have also cost a lot more money.

So cheers. Remind me to buy the Ansible & Let’s Encrypt folks a beer or two. They’ve certainly earned it.


Apache HTTPD and LDAP attributes

More fun with Apache HTTPD. This time I wanted to access a user’s email address and user id, both stored in LDAP, in an application without LDAP integration. Thankfully, this turned out to be pretty easy. But first, some background.

The application is a very simple Java application, which sits behind HTTPD as a Reverse Proxy. The Reverse Proxy handles authentication, because reinveting the wheel gets a bit dull after a while, and mod_authnz_ldap works just fine. But while adding a feature to the application, I suddenly needed access to the user’s userid and email address, for Reasons™.

The documentation turned out to be somewhat helpful, if cryptic. There is a relatively easy way to expose LDAP attributes to CGI scripts using mod_authnz_ldap — which can set environment variables for perusal by the CGI script. Using the same mechanism in combination with a reverse proxy was a bit trickier; after all, the Java application is running in a Jetty container and it talks to the proxy using HTTP. That doesn’t really offer room for injecting environment variables.