Dynamic Device Classes in Python

When writing PyTango device servers, it is common to implement one Python class per Tango device class. For small projects, this approach is simple and easy to understand. However, it becomes cumbersome when the set of available devices is not known at development time.

Consider a device server that should be entirely driven by a configuration file. Instead of hard-coding every supported device class, the server reads the device definitions at startup and creates the required Tango device classes automatically.

The goal is to write a generic PyTango server that does not need to be modified whenever a new device class is added. If a new entry appears in the configuration file, the server should simply create the corresponding Tango device class.

Python type

At first glance, this sounds unusual. After all, Python classes are typically defined using the familiar class keyword:

class Motor(Device):
    pass

Most Python developers stop here and never think about how classes are actually created. Under the hood, however, classes are objects themselves, and Python provides a built-in mechanism to construct them dynamically.

The function responsible for this is type().

Most of us use it in its simplest form to inspect the type of an object:

print(type(42))
# <class 'int'>

Less well known is its three-argument form:

type(name, bases, attributes)

The arguments are:

  • name: the name of the new class.
  • bases: a tuple of parent classes.
  • attributes: a dictionary containing class attributes and methods.

This means that the following definition is equivalent to the above class definition.

Motor = type(
    "Motor",
    (Device,),
    {},
)

The resulting object is exactly the same: a Python class that can be instantiated or registered with PyTango.

The third argument of type() becomes particularly interesting when more than just the class name should be configurable. The attributes dictionary allows methods, properties, or other class members to be added dynamically. This can be useful when Tango attributes or commands are also described in the configuration file.

Motor = type(
    "Motor",
    (Device,),
    {
        "some_property": 42,
    },
)

a = Motor()
print(a.some_property)
# 42

Conclusion

For our use case, creating classes dynamically becomes straightforward. Reading the configuration file and creating the required classes can be done in a simple loop. From PyTango’s perspective, there is no difference between a statically defined class and one created dynamically using type(). Both behave like ordinary Python classes.

AI Code Won’t Be for Humans Much Longer (AI impressions, part 2 of 5)

This is the second part of the series “Impressions of Our Current AI Usage”, as outlined by the introduction article.

In the first years of software development, the word “source code” didn’t exist, because code was just that: encoded machine instructions. How they were encoded changed rapidely, from flipping bits in the RAM directly by mechanical switches over feeding paper tapes with punched holes to magnetic storage. But for a long time, we worked with none or little abstraction over the actual machine code. I remember assembler code listings that had two columns of text: the first column for the computer, the second one just translating the first column into human-readable text.
And even with this little bit of clarification what the code actually does, we already needed additional software that took our source code and translated it for the machine.
With the adoption of higher-level programming languages, the additional software stack grew in depth until the distance between the source code and the actual machine instructions was big enough to warrant an intermediate layer of representation. Programming languages like Java or C# put a “byte-code layer” between our textual source code and the binary machine code. The machine we program against is no longer a real computer, but a “virtual machine” or in better words, a model of a machine. As long as we write source code that works correct with the model, we can assume that all the translation layers will find a way to run it correctly on the real computing substrate.
We are used to this kind of programming. We describe our goals using the machine model and a sophisticated machinery of software and hardware parts make it happen.

Forward to today and we use artificial intelligence (or inference using another kind of “model”) to produce source code in our favorite programming languages by describing our goals in even broader terms than before. We might mistake our prompts for natural language and think that we are able to produce source code by just saying what we want.

The question that poses itself nearly instantly is: If we invented a device that transforms natural language into machine behaviour from scratch today, would we include all the intermediate layers into its inner works? Is it really a good idea to transform natural language into higher-level source code, compile the source code into byte code and do all the weird magic to come up with a sequence of machine instructions that resemble the byte code? Isn’t it more efficient to teach the inference how the actual machine works and let it program directly?
Or, asking from the other side, who is the target audience of the generated source code if nobody reads it and the compiler only parses it once? Why does the inference invent all the variable and method names when the compiler throws them away again in the first step of its processing? Of course, right now the inference only imitates our way of working. But we work like we do because we write source code for other humans. If the human at the helm can’t read any layer of code anyway, why not jump directly to the most obscure representation of code and skip all the readability requirements?

As soon as the inference doesn’t imitate but actually learns about the target computing substrate, it will produce working code that is undecipherable for human readers, but optimal for the machine. (If you want to experience this effect in a tiny dose, I encourage you to play the “TIS-100” programming game). And because most inference users don’t need the readable code anyway, they won’t miss anything and get faster solutions with less hassle.

So my guess is that today’s source code will be a dying art, invented for humans and ignored by the machines because it doesn’t provide anything useful for them. The source code of the future will be less readable, more enigmatic and probably more efficient for the machine. Which means that human intervention or even just participation in the software production process will become more cumbersome and therefore even more limited.

My sorrow is that this distancing of the programming process from actual human oversight might provide a hard depedency on inference work alone. It would mean that humans aren’t just scales slower than the inference, but actually incapable of doing its work by hand anymore.

Quotes Are Not Part of the Argument

Recently, we upgraded a project from an older Java version to a newer one. Many of the changes were routine: update dependencies, replace deprecated APIs, fix a few compiler errors and run the test suite.

One of these changes concerned the invocation of an external Windows program.

The application used the deprecated overload:

Runtime.getRuntime().exec(command);

The command was assembled as one long string. The external program accepted command-line parameters of the following form:

/a="value of A" /b="value of B" /c="value of C"

Because the parameter values could contain spaces, we enclosed them in double quotes. We even had unit tests that explicitly verified the quoting. It was an important detail, or so we thought.

As part of the upgrade, we switched to the recommended overload that accepts the executable and its arguments separately:

Runtime.getRuntime().exec(commandArray);

The migration seemed straightforward. Instead of joining the executable and all parameters into one command string, we put them into a string array:

String[] command = {
"program",
"/a=\"value of A\"",
"/b=\"value of B\"",
"/c=\"value of C\""
};
Runtime.getRuntime().exec(command);

The unit tests were still green. The quotes were still present. Everything looked correct.

Then the application was deployed.

Invalid switch

In production, the external program stopped accepting our invocation. Its only diagnostic message was:

Invalid switch

This was not particularly helpful.

We inspected the parameters. All switches were present. Their spelling was correct. Their order was correct. The values were correct. The quote characters were exactly where our tests expected them to be.

Even more confusingly, the command worked perfectly when entered manually in a Windows command prompt:

program /a="value of A" /b="value of B" /c="value of C"

The executable clearly supported these parameters. The shell command clearly worked. And our Java code appeared to produce the same command.

But it did not.

Asking the receiving program

After spending some time comparing strings and staring at quote characters, we decided to stop reasoning about what the external program ought to receive. Instead, we wrote a small program that showed us what it actually received:

void main(String[] args) {
for (var i = 0; i < args.length; i++) {
System.out.println("args[" + i + "]: '" + args[i] + "'");
}
}

We packaged it as a JAR and invoked it from cmd.exe using the same parameter structure:

java -jar exec-experiment.jar /a="value of A" /b="value of B" /c="value of C"

The output was:

args[0]: '/a=value of A'
args[1]: '/b=value of B'
args[2]: '/c=value of C'

The double quotes were gone.

This was the missing piece.

The quotes in a shell command are not necessarily characters intended for the receiving program. They are instructions to the command-line parser. They tell it that a sequence containing spaces belongs to one argument.

The value

/a="value of A"

does not mean that the program receives an argument containing two quote characters. It means that the program receives one argument rather than three:

/a=value of A

The quote characters control parsing. They are not part of the resulting argument.

Quotes can appear in surprising places

To verify this interpretation, we performed a slightly more unusual experiment:

java -jar exec-experiment.jar /"a="va"lue of A" /b="value of B" /c="value of C"

This command is certainly not how anybody would normally write the parameters. Nevertheless, its output was unchanged:

args[0]: '/a=value of A'
args[1]: '/b=value of B'
args[2]: '/c=value of C'

The quotes can surround different portions of a token. Their purpose is to influence how the command line is divided into arguments. Once parsing is complete, they disappear.

This distinction is easy to overlook because a command line is usually presented as a string. It looks as though this string is passed to the program. In reality, there are two different representations involved:

program /a="value of A"

is a textual command line that still needs to be parsed.

By contrast,

new String[] {
"program",
"/a=value of A"
}

already describes the result of that parsing: an executable followed by one complete argument.

We had moved the parsing boundary

With Runtime.exec(String[]), every array element already represents one argument. Spaces inside an element do not split it into additional arguments.

By retaining the quotes, we had changed their meaning. They were no longer syntax interpreted by a shell-like parser. They had become literal characters inside the argument:

/a="value of A"

That was not what the external program expected. It expected:

/a=value of A

The error message “Invalid switch” was therefore accurate, but not very illuminating. The switch looked correct in our logs because we were looking at its command-line representation rather than at the argument format expected by the program.

The fix was simple:

String[] command = {
"program",
"/a=value of A",
"/b=value of B",
"/c=value of C"
};
Runtime.getRuntime().exec(command);

Or, preferably, using ProcessBuilder:

Process process = new ProcessBuilder(
"program",
"/a=value of A",
"/b=value of B",
"/c=value of C"
).start();

After removing the quote characters, the external program worked again.

Ironically, the quotes that our old implementation and its tests had treated as essential were exactly what broke the new implementation.

The takeaway

A command line and an argument array are not interchangeable representations.

When constructing a command line, quoting may be required to preserve spaces during parsing. When constructing an argument array, parsing has already happened conceptually. Each element is one argument, spaces included.

Do not ask:

How would I type this command in a shell?

Ask:

What exact strings should the receiving program find in its argument array?

The answer to the second question is what belongs in Runtime.exec(String[]) or ProcessBuilder.

Sometimes an API migration changes more than a method signature. It moves a boundary – in this case, the boundary between formatting a command line and supplying already separated arguments.

And when that boundary moves, yesterday’s carefully tested solution can become today’s bug.

When Optional sounds too optional: opt for more expressive types

So, I have one PyQt application which not only is quite data-heavy, but also has significant real-time requirements, as well as multiple windows. This construct brings some absolutely horrifying highly intellectually inspiring quests with it, and Python turned out to be kind of a good decision for that project, because that is one of the languages where, when you think about your structure a bit, you might get to write very natural-sounding like code.

Of course, the following idea is actually language-agnostic, I will just use fictive Python examples close to problems-based-on-a-true-story.

This in itself is not only a matter of aesthetics, but because real-time demands are quite tricky to reliably be covered by unit tests alone, the actual code has to read itself so clearly that one does not need to second-guess what any of this does. Think of a bedtime story, which usually would not, coming to think of it, contain clauses – or paragraphs, for that matter – requiring, under circumstances not even trivial to the human eye, one kind of meticulous gymnastics, easily negating twice, or thrice, and relying on Python’s borderline criminal degrees of freedom in duck typing, or canards even– you see — your toddler will now not go to sleep anytime soon. Or trust you with another story, for that matter.

Now I found out: While Qt is somewhat mature, one cannot even trust their way of doing things – i.e. turns out, the signals/slots system is not particularly designed for performance. Neither did I feel inclined to put my faith into even another state management solution like e.g. python-statemachine package, because – as capable as that sounds, it might be overkill, and distracting with its own idiosyncrasies (as also: I would not recommend Redux for a web project anymore, especially in TypeScript, except for you really know from the start that this is a good fit).

But, so, I have some tricky interplays between

  • Data consistency / single-source-ness demands that e.g. between two windows, there should only be primitive data exchanged, say str/int identifiers, and both have access to their repositories; not throwing loaded data sets around my memory in order to go stale at times
  • Comprehension, most significantly Single Level of Abstraction, or other indicators of mental load like how many levels of intendation / return paths are mixed within sight (and also, Type Annotations do help a lot in Python, even though not mandatory, i.e. the complete opposite of fighting Redux-TypeScript-chimaeras – but I digress…
  • Robustness, where I would believe that my user (me) has virtually no chance of even seeing this and that window when their data is maybe still loading somewhere – but I still check these cases, because this bedtime story has no business in leaving you an hopeful-to-anxious pile of nerves
  • Traceability of your state, for troubleshooting and useful UI feedback (as you’d guess, real-time event based stuff is not easily debugged by break points or logging alone).

So over months in that project, I grew annoyed of code like (Symbolbild)


class Editor:
    # ...
  
    def load_editor(self, params: Optional[EditorParams]):
        if params and (self._entity is not None or
                       self._entity.id != params.id):
            if entity := repository.load_entity(params.id):
                self._entity = entity
            else:
                raise ValueError("repository needs some alone time :(")
            self._entity.other_stuff = other_repo.check_stuff()
        elif params is None:
            raise TypeError(
                "sounds Optional in our signature, but actually is not"
            )
        elif self._entity.id == params.
            self.adjust_more_stuff(self._entity, params.stuff)
            # ...

Because encountering any single block of these drags you down, I have currently accustomed myself to write these as (one can argue whether the names like “Supplier” are the best here, but they’re not the worst, I believe)

@dataclass(frozen=True)
class LoadedEntity:
id: str
entity: Optional[Entity]
stuff: Optional[OtherStuff]
@property
def is_unusable(self):
return self.entity is None
@property
def missing_stuff(self):
if self.is_unusuable:
return True
else:
return self.stuff is None
class EntitySupplier:
_current: LoadedEntity
_entity_repo: EntityRepository
_stuff_repo: OtherStuffRepo
# __init__ etc. hereby left out as boilerplate
def load_params(self, params: Any):
# do all your checks in here
if (... very bad ...):
self._current = LoadedEntity(params.id, None)
return
entity = self._entity_repo.get(params.id)
stuff = self._stuff_repo.get(params.stuff).for(entity)
return LoadedEntity(
params.id,
entity,
stuff
)
@property
def entity(self):
return self._current.entity
def expecting(self, stuff: bool = False) -> Optional[LoadedEntity]:
if stuff and self._current.missing_stuff:
return None
return self._current
class Editor:
_supply: EntitySupplier
_logger: SomeLogger
def __init__(self, **kwargs):
self._supply = EntitySupplier(**kwargs)
self._logger = BlaBlaLogger()
def load(self, params):
self._supply.load_params(params)
if entity := self._supply.entity:
self.update_ui(entity)
else:
self._logger.error("Outsmarted, eh? %s | %s", str(params), stack_trace())
return
if supply := self._supply.expecting(stuff=True):
self.initiate_stuff_from(supply)
else:
self._logger.info("Entity %s is ready, Stuff is not | %s", str(entity), stack_trace())

So, the LoadedEntity serves like a concatenation of several Optional types, but it wraps the logic (i.e. there’s no sense in having stuff when you don’t have entity first) instead of just shruggingly claming “well, this entity here is optional – and that other stuff is, too”. Now, LoadedEntity is not a pretty name at all (have a better one?), but it sure beats having two straightaway lies.

I like that pattern because it allows me to stash the EntitySupplier and LoadedEntity somewhere on their own (I do strictly not believe that every class needs its own file, but some of the “Single …” ideas (Responsibility, Level of Abstraction, you name it) do also apply here; and the Editor.load(…) itself does read somewhat like a short story. It has quite linear structure and can early-return, and/or log, on demand, and while naming is still hard (consistently voted one half of famous Hard Things), I could even have some fun in designing that language while preserving the idea, that future-me can arrive in a few weeks (read: hours) and still trust in some of the entites and stuff.

The quintessence here is: Checking for None (which is Python’s NULL, and the typing Optional[T] is identically equal to T | None) is still a thing in 2026 due to its sheer practicality, but if you design some some structure around that and keep these checks in something like LoadedEntity, you can keep the abyss from staring back into you.

Rails Strict Locals: Giving Partials an Explicit Interface

Rails partials are a great way to reuse view code, but they have traditionally suffered from one weakness: their interface is implicit.

When opening a partial written by another developer, it is often unclear which locals are required, which are optional, and whether all of them are still used. IDEs typically cannot help much either, often showing warnings about unresolved variables because they cannot determine where the values come from.

The problem becomes even more apparent as an application grows and partials are rendered from multiple places.

If a local is forgotten by call, the error only appears when the template is rendered:

undefined local variable or method `missing_local'

If an extra local is passed, Rails traditionally ignores it.

Over time this creates a situation where the real API of the partial exists only in the heads of the developers maintaining it.

Rails Strict Locals

Rails provides a feature called strict locals that allows a partial to declare its expected interface:

<%# locals: (title:, highlight: false) %>

The declaration resembles Ruby keyword arguments and is placed at the top of the template.

A local like title without a default value is required. Locals like highlight with default values become optional

The partial now documents and enforce its own API. If a required local is missing, Rails raises an exception instead of failing later when the variable is accessed. Likewise, if a caller provides a local that is not declared, Rails rejects it.

Conclusion

Strict locals do not fundamentally change how partials work, but they make them easier to understand and maintain.

By declaring the expected locals directly in the template, partials become self-documenting and gain an explicit contract with their callers. Missing locals are detected early, obsolete locals are rejected, and developers no longer have to search through controllers, parent templates, and render calls to understand where variables come from.

An additional benefit is improved tooling support. Once the interface of a partial is explicit, IDEs can understand the available variables much better. Your IDE becomes a helpful companion again rather than a source of noise.

A doubly linked list for entt components

I recently implemented a small CRTP template to group entities in entt. It turned out quite nicely, so let me share it here:

template <class T> class doubly_linked_component
{
public:
using node_type = doubly_linked_component<T>;
static void on_construct(entt::registry& entities,
const entt::entity e)
{
auto& that = entities.get<T>(e);
that.next_ = that.prev_ = e;
}
static void on_destroy(entt::registry& entities,
const entt::entity e)
{
auto& that = entities.get<T>(e);
// List has only this element?
if (that.next_ == e)
{
return;
}
auto next = that.next_;
auto prev = that.prev_;
auto& next_node = static_cast<node_type&>(entities.get<T>(next));
auto& prev_node = static_cast<node_type&>(entities.get<T>(prev));
prev_node.next_ = next;
next_node.prev_ = prev;
}
static void merge(entt::registry& entities,
entt::entity lhs, entt::entity rhs)
{
auto& lhs_node = static_cast<node_type&>(entities.get_or_emplace<T>(lhs));
auto& rhs_node = static_cast<node_type&>(entities.get_or_emplace<T>(rhs));
// The end of left (which is left.prev_) needs to point to right
auto lhs_end = lhs_node.prev_;
auto rhs_end = rhs_node.prev_;
auto& lhs_end_node = static_cast<node_type&>(entities.get<T>(lhs_end));
auto& rhs_end_node = static_cast<node_type&>(entities.get<T>(rhs_end));
lhs_end_node.next_ = rhs;
rhs_node.prev_ = lhs_end;
rhs_end_node.next_ = lhs;
lhs_node.prev_ = rhs_end;
}
static std::generator<entt::entity> enumerate(entt::registry& entities,
entt::entity e)
{
// By default, entities are their own lists
if (!entities.any_of<T>(e))
{
co_yield e;
}
else
{
auto current = e;
while (true)
{
co_yield current;
current = entities.get<T>(current).next_;
if (current == e)
break;
}
}
}
private:
entt::entity prev_ = entt::null;
entt::entity next_ = entt::null;
};

You can use it like this:

struct cool_group : doubly_linked_component<cool_group> {};

By default, each entity represents its own 1-sized group. To merge two groups:

cool_group::merge(entities, left, right);

To iterate over all entities, I am using the new std::generator and coroutines. You can use it like this:

for (auto entity : cool_group::enumerate(entities, head))
do_something(entity);

entt will automatically call into on_construct and on_destroy.

People nowadays usually avoid linked lists because of all the pointer chasing required to actually use them. The pointer chasing is not the problem though, the non-locality is. If you make sure your nodes are all allocated in memory close to each other, there is hardly a penalty. entt will usually do this if nodes are also allocated close in time, which is often the case if you want to group things, so this works nicely in that regard, too. Feel free to use this code under CC0.

Caching Conan Dependencies in Docker for Faster Builds

One problem I often have when dockerizing my C++ Jenkins CI projects is handling incremental builds, for both our own code and the dependencies. Starting builds from scratch can take tens of minutes, too long for my taste.

My build stack is usually conan as a dependency manager and CMake/Ninja for building. Conan will usually try to download precompiled dependencies, but often enough, those are not available for my specific combination of compiler settings and flags, so it’ll build them on demand with the --build=missing flag. That usually takes the bulk of the time needed for a full build. So it makes sense to keep the dependencies cached, once they are built. However, since we use Docker to setup the build environment, they are all lost by default.

Who Owns What?

The obvious solution is to mount a folder on the build host to keep the conan cache using the -v / –volume option for docker run. This can be done by setting the CONAN_HOME environment variable, and I usually use one cache per build folder, which seems like a good compromise between speed and isolation.

But that causes other problems: docker will create all the files for the user inside the container, which is root by default, creating a whole bunch of files that the CI host user cannot delete, e.g. when a branch gets deleted. This breaks the CI setup to a point where manual intervention is required. A somewhat simple clutch is the -u user:group option to docker run, which will execute the build with the given user. The problem I was having with that, however, was that this user did not have access to user-scoped tool installations like conan via pipx.

User-specific Images

My current strategy to deal with this is to inject the host CI user and group into the docker ‘builder’ image, and then do all the building in the container using that user, as if using the CI host user on the metal. The Dockerfile looks like this:

FROM gcc:14.1-bookworm
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get -y dist-upgrade
RUN DEBIAN_FRONTEND=noninteractive apt-get update && apt-get -y install \
cmake \
debhelper \
ninja-build \
python-is-python3 \
python3-pip \
pipx
ARG HOST_USER_ID
ARG HOST_GROUP_ID
RUN groupadd -g ${HOST_GROUP_ID} hostgroup && \
useradd hostuser -u ${HOST_USER_ID} -g ${HOST_GROUP_ID} -m -s /bin/bash && \
mkdir /conan_home && chown hostuser:hostgroup /conan_home
USER hostuser
ENV PATH="$PATH:/home/hostuser/.local/bin"
RUN pipx install conan
ENV CONAN_HOME=/conan_home
WORKDIR /build_root
# Build the viewer deb package
CMD ["/source_root/build.sh"]

After doing the user-independent setup, this declares two ARGs for retrieving the user and group IDs, and then sets up a user with those in the docker image, calling it hostuser:hostgroup internally. Note that the names will not leak out of the container, only the IDs do.

It installs conan via pipx as that user and makes sure it is in the PATH for the build later. This is the real advantage of passing the user into the image creation: user specific things can be installed!

In our Jenkinsfile, I build the image from that while injecting the current user via the –build-arg option:

docker build . --iidfile docker_image_id \
--build-arg HOST_USER_ID=`id -u` \
--build-arg HOST_GROUP_ID=`id -g`

This expects three folders to be mounted: /source_root for the sources/repository, /build_root for the out-of-source build, and /conan_home for the conan cache. Important: make sure these folders are created by the CI user before passing them to docker, or it will create them with the wrong owner. I’m only creating the latter two, since the first one is obviously created by Jenkins.

mkdir -p docker/build docker/conan

Once the folders are set up and the image is built, I run the actual build in a container via:

docker run --rm \
-v `pwd`:/source_root:ro \
-v `pwd`/docker/conan:/conan_home \
-v `pwd`/docker/build:/build_root \
`cat docker_image_id`

That should run the actual build and populate the conan cache. After that I extract the artifacts I need and remove the docker image and ID file with:

docker image rm `cat docker_image_id` && rm docker_image_id

And we’re done!

The future of Grails

Many long-term readers of our blog may have noticed a post about Grails Framework topics every now and then. We are using Grails for more than 15 year both in customer projects and internal ones.

Sometimes using the framework was fun, productive and bliss. At other times it could be frustrating upgrading, chasing bugs or finding workarounds. Occasionally, performance could be a problem. Most of the time it was a solid framework with solid output and customer value.

The more recent past

Ownership/stewardship of Grails changed several times over the years from one company to another. Updates were very infrequent, the general direction was very unclear and the future of the framework extremely uncertain.

Because all of the above we did not start new projects using Grails and did not recommend it to potential customers but instead used other frameworks like Micronaut, Javalin or .NET.

But suddenly there was light at the end of the tunnel: Grails was handed over to the Apache Software Foundation (ASF) in the middle of 2025 and became a top-level project there. That by itself may not be a complete turnaround and rescue for the framework, but it certainly sparked a bit of hope into the whole situation.

The presence and future

Since the adoption of Grails by the ASF a lot has changed. Tons of work has been going on in the background to streamline future development, work on reproducible builds (as required by the ASF) and to enable frequent releases, updates and improvements.

Grails 7 was born and release under ASF stewardship. The community is more open than ever and it seems to be growing again after years of stagnation or even decline.

The roadmap and plans for the next months is clear and the project is moving with a steady pace towards the goals. Of course, there is a lot of work to do, like reviving and porting several plugins to enable all users to migrate to Grails 7 and beyond – but it is happening.

Conclusion

If all the positive changes under the stewardship of the ASF continue, the future of Grails can be bright for the years to come. At least, it does not pose a liability or risk for its users and their customers anymore.

In my opinion, two things besides all the technical stuff are most important:

  • The strong commitment of the ASF to further develop and enhance the project provides safety for developers and customers investments
  • The mindshift from an exotic framework to a productive, JVM-based framework leveraging standard technologies from the java/spring/hibernate ecosystem provides familiarity and stability

Imho, this quote of the Project Management Committee (PMC) chairman James Fredley says it best:

Grails is NOT an exotic outlier. It’s PRODUCTIVITY LAYERS on top of SPRING BOOT.

Out of Memory when loading Records in Rails

Recently I ran into a problem that only showed up outside the development environment.

I had a small script that needed to iterate over all records in the database and load blobs.

Document.all.each do |doc|
process(doc.blob)
end

With a small dataset everything worked as expected.
With production-sized data, however, the job was terminated by the runtime with an out-of-memory error.

This behaviour is not surprising once you look at what all.each actually does.

How all.each works

When calling all.each ActiveRecord is loading the complete result set into memory before the iteration starts.
For large tables this means that thousands or even millions of Ruby objects are instantiated at once.

If each record also references additional data — for example blobs, attachments, or associations — the memory usage grows quickly.

Loading Records with find_each

ActiveRecord provides find_each for exactly this scenario:

Document.find_each do |doc|
process(doc.blob)
end

In contrast to each, this method does not load all records at once.
Instead, records are fetched in batches and yielded one by one.

Conceptually the process looks like this:

  1. Load a limited number of records
  2. Yield them to the block
  3. Discard them
  4. Load the next batch

By default, find_each loads records in batches of 1000.
The batch size can be configured:

Document.find_each(batch_size: 100) do |doc|
process(doc.blob)
end

find_each always iterates in primary key order. This means the model must have a primary key that is orderable like integer or string. Any explicit ordering will be ignored.

If more control is required, find_in_batches can be used instead. It requires manual iteration over the batches.

Conclusion

Iterating over large tables with all.each is easy to write but can lead to excessive memory usage once the dataset grows.

For batch processing tasks, find_each is usually the safer default because it limits the number of instantiated records and keeps memory usage predictable.

C# is very strict about modify-while-iterating

Today I stumbled upon some behavior in C#’s List<> that I found very surprising. I had forgotten about RemoveAll() and basically implemented it myself:

var target = 0;
foreach (var each in list)
{
if (!predicate(each))
list[target++] = each;
}
list.RemoveRange(target, list.Count - target);

Apparently, this is not allowed. You cannot assign to any element in the List<> while you are iterating/enumerating it: The List<> implementation holds a ‘version’ number that is incremented any time a change is made, including assignments. When the Enumerator is advanced via MoveNext it checks for this version and throws the dreaded ‘Collection was modified’ exception.

Except that there shouldn’t really be a problem here, and the modification checking code is basically being too coarse. This code ‘compacts’ the list, copying elements where the predicate evaluates to true to the front of the list, and then cutting off the rest of the elements. There’s never really any doubt what each references. In fact, in other languages, this approach is even considered idiomatic to remove elements while iterating. In ancient C++, this is known as the remove/erase idiom, see also std::erase.

So why did the library designers of C# consider setting a value while iterating a problem? I don’t know, but at least now I have a story to remind of of RemoveAll()‘s existence.