This action will delete this post on this instance and on all federated instances, and it cannot be undone. Are you certain you want to delete this post?
This action will delete this post on this instance and on all federated instances, and it cannot be undone. Are you certain you want to delete this post?
This action will block this actor and hide all of their past and future posts. Are you certain you want to block this actor?
This action will block this object. Are you certain you want to block this object?
i added some in-process data collection to ktistec in order to better understand how it uses memory.
the chart shows the accumulated total requested memory (blue) over time. as expected, it grows monotonically and almost linearly. in theory, i guess, if i posted something engaging, you'd see the effect of the engagement (likes, shares, follows, etc. etc. etc.) on memory usage. in any case, the heap (red) remains flat.
i think it would be great to have this chart on the metrics page. when time permits, i'll add it. in the meantime, if you're running a (very) recent build, you're collecting data.
i'm currently working on a few ktistec enhancements in parallel.
there are ~13 instances currently running that i'm aware of, so there's also a solid stream of bugs/enhancements coming in 😉 thanks everyone!
I've finally fixed a mysterious bug that was reported while I was traveling. I thought it would be interesting to share the investigation and resolution. Plus, it's an excuse to do some writing...
Rules-based logic has been part of ktistec for a while, and the rules engine has been running successfully in production for me and others, so I was surprised to learn that the recent introduction of a new rule resulted in spurious notifications. The rule in question is supposed to create a notification when someone replies to one of your posts. Instead, the rule was creating a notification when anyone replied to anything.
rule "create/reply" condition activity, IsAddressedTo, actor condition CreateActivity, activity, object: object condition Object, object, in_reply_to: other condition Object, other, attributed_to: actor none Notification, owner: actor, activity: activity assert Notification, owner: actor, activity: activity end
I had unit tests for the rule's logic, and the logic seemed correct when I visually inspected the rule again. To top it off, I wasn't able to find a set of steps that reproduced the problem locally.
For various poor reasons, I hadn't tried the rule in production myself. With no other obvious path forward, I deployed it and waited. Sure enough, the bug surfaced—along with a stack trace helpfully correlated with every occurrence of the bug. Jackpot—or so I thought!
Exception: relationship: already exists (Ktistec::Model::Invalid) from /workspace/src/framework/model.cr:650:9 in 'save' from /workspace/src/framework/model.cr:649:7 in 'save' from /workspace/src/rules/content_rules.cr:49:3 in 'assert' from /workspace/src/utils/compiler.cr:118:5 in 'assert' from /workspace/src/utils/compiler.cr:39:19 in '->' from /workspace/lib/school/src/school/rule/rule.cr:38:23 in 'call' from /workspace/lib/school/src/school/domain/domain.cr:158:9 in 'run' from /workspace/src/rules/content_rules.cr:89:5 in 'run' from /workspace/src/controllers/inboxes.cr:248:5 in '->' ...
The stack trace was curious for two reasons. The error occurred when creating (asserting) the notification because a notification already existed, which 1) shouldn't be possible because the assertion is preceded by a guard condition that ensures that the notification does not exist! And of course, 2) the notifications were spurious—rule evaluation shouldn't have resulted in a notification in the first place...! Luckily for me, it soon got even weirder.
It's possible to trace rule evaluation. Turning tracing on revealed a surprising mystery: 3) thousands of successful activations of the "create/reply" rule for any given reply. Thousands! That did explain something, though. The first successful activation created the spurious notification. The second activation raised the error (because the notification had just been created). When evaluating rules, all matches are first found, and then all actions are taken, therefore, in this case, the guard condition couldn't have had any effect. The error also terminated any further action processing, so there was only one stack trace.
So, I swapped one curiosity for a mystery—but why were there thousands of matches for that rule? There should have been only one (really, none).
A trace prints information about conditions and the facts that match, along with information about the values that are bound to variables in the process.
Rule create/reply Condition School::BinaryPattern(ContentRules::IsAddressedTo, School::Expression, School::Expression) Match ContentRules::IsAddressedTo, bindings: activity=#<ActivityPub::Activity::Create iri=...> actor=#<ActivityPub::Actor::Person iri=...> [] Condition ContentRules::CreateActivity Match #<ActivityPub::Activity::Create iri=...>, bindings: object=#<ActivityPub::Object::Note iri=...> [activity=#<ActivityPub::Activity::Create iri=...> actor=#<ActivityPub::Actor::Person iri=...>] Condition ContentRules::Object Match #<ActivityPub::Object::Note iri=...>, bindings: [activity=#<ActivityPub::Activity::Create iri=...> actor=#<ActivityPub::Actor::Person iri=...> object=#<ActivityPub::Object::Note iri=...>] Condition ContentRules::Object Match #<ActivityPub::Object::Note iri=...>, bindings: other=#<ActivityPub::Object::Note iri=...> [activity=#<ActivityPub::Activity::Create iri=...> actor=#<ActivityPub::Actor::Person iri=...> object=#<ActivityPub::Object::Note iri=...>] ...
Hmmm, that third condition... Nothing is bound to the variable other
, as expected, yet the condition is treated as having successfully matched a fact. That's obviously incorrect—other
should be the post being replied to. Or the condition should fail and rule evaluation terminate. Instead, since it is not bound but evaluation continues, other
is free to be bound as necessary to satisfy the fourth condition. Which is exactly what happened.
The fourth condition matches posts that are attributed to me. Unless this condition is otherwise constrained—say, by a variable bound in the previous condition—there are going to be thousands of matches. Sokath, his eyes opened!
So, here's the fix. It's in the code that binds variables to values. in_reply_to
is an association on a post (object) that links it to the post (object) it's a reply to. This code is inside a block that processes every potential match.
{% for association in associations %} if @options.has_key?({{association.id.stringify}}) if (target = @options[{{association.id.stringify}}]) && (name = target.name?) && !temporary.has_key?(name) - if (value = model.{{association.id}}?(include_deleted: true, include_undone: true)) - temporary[name] = value - end + next unless (value = model.{{association.id}}?(include_deleted: true, include_undone: true)) + temporary[name] = value end end {% end %}
The post that's being replied to isn't always cached locally. When it's not, the association returns nil
and nothing is bound. That's okay. But previously, the match was also considered to be successful! The solution... treat it as a failure and proceed to the next potential match.
well... i'm going to find out if i've got indexes in all the right places...!
this chart shows inbound activitypub messages to my server. over time fediverse instances seem to receive increasing numbers of messages, many not addressed to anyone on the instance, so this represents an increase in that passive traffic, replies to other people's posts, as well as announcements/boosts/shares.
n.b. you can see all of the inbound messages to your ktistec server on the /everything endpoint.
among other improvements, document how rules work.
it's now possible to customize how ktistec processes ActivityPub inbound and outbound activities without having to write crystal language and recompile the server.
i'm still making lots of small changes/improvements to ktistec: upgrading dependencies, fixing bugs, and cleaning up. i'm laying the foundation for improvements to the experience of reading posts on your ktistec instance—a reactive user interface with streaming updates/notifications, the ability to follow arbitrary collections of objects, and a timeline that can flexibly highlight content you find interesting (vs. displaying posts in chronological order). underpinning all of this is a flexible, scriptable, rule-based language for customizing the experience.
ktistec uses sqlite as its datastore. it’s a smart idea to back up the sqlite database periodically. unfortunately you can’t just copy the database file, at least while the server is running.
this article explains how to make a backup safely, and how to set up automatic, scheduled backups with cron.