Skip to content

systemd notify support#999

Open
mbr wants to merge 18 commits into
mahkoh:masterfrom
mbr:sd-notify
Open

systemd notify support#999
mbr wants to merge 18 commits into
mahkoh:masterfrom
mbr:sd-notify

Conversation

@mbr

@mbr mbr commented Jun 12, 2026

Copy link
Copy Markdown

I was trying to setup jay through systemd, along with other services depending on it (e.g. starting Signal through a user unit). The initial approach was simple enough, just make the Jay user service Type=notify and send the readiness notification manually in on-graphics-ready.

This comes with a catch though: Since the compositor process forks from the main (reaper) process, NotifyAccess=all is necessary, allowing other processes to signal readiness.

Things took an unexpected turn when I started PostgreSQL in a terminal -- after ending it, the NOTIFY_SOCKET envvar was still intact, it dutifully sent STOPPING=1 down the shared(!) socket, dropping me back to the login greeter rather abruptly.

The solution would be proper systemd notify integration, here's my take on it:

  1. If NOTIFY_SOCKET is set, after spawning the compositor, we send MAINPID=..., which signals systemd to treat the compositor as the main process. This allows us to keep NotifyAccess=main.
  2. Once the compositor starts up, just before executing on-graphics-initialized, it sends READY=1, signaling systemd that it has started successfully.
  3. To not confuse spawned programs, immediately after the compositor forks, we remove NOTIFY_SOCKET from its environment (and carry it forward).
  4. To avoid the race condition where the compositor reaches graphics-ready before the reaper had a chance to send MAINPID=..., we install a barrier pipe and wait for it to close. Slim chance, but it would lead to a stuck service otherwise and a frustrating bug to find.

The only potential side-effect for systemd Type=notify users is that now the compositor instead of the reaper is considered the main process.

Happy for any feedback, if this is not a feature you want in jay, feel free to close it. I also wasn't sure about the AI policy of this project, so for now, this PR consists of free-range, organic hand-written bytes only, although an LLM was used to explain the codebase initially, which I believe is in spirit with the generated docs. I think clarification on slop tolerance for this project couldn't hurt :)

@mahkoh

mahkoh commented Jun 13, 2026

Copy link
Copy Markdown
Owner

This looks good in principle. A few things:

Create a new utility fd_blocker.rs with the following API:

pub struct FdBarrier(pub Rc<OwnedFd>);

pub struct FdBlocker(pub Rc<OwnedFd>);

pub fn create_fd_blocker() -> Result<(FdBarrier, FdBlocker), OsError> {
    todo!()
}

impl FdBarrier {
    pub fn wait_blocking(&self) -> Result<(), OsError> {
        todo!()
    }
}

FdBarrier should contain the write end of the pipe and you can detect that the read end has been closed by polling the write end with no interest. POLLHUP will be delivered.

Change the API of sd_notify.rs as follows:

pub struct SdNotify {
    fd: Rc<OwnedFd>,
}

impl SdNotify {
    pub fn open() -> Result<Option<SdNotify>, OsError> {
        todo!()
    }

    pub fn main_pid(&self, pid: c::pid) -> Result<(), OsError> {
        todo!()
    }

    pub fn ready(&self) -> Result<(), OsError> {
        todo!()
    }
}

open should consume the environment variable and open a connection, if possible.

@mahkoh

mahkoh commented Jun 13, 2026

Copy link
Copy Markdown
Owner

For a different reason, I've added the fd_blocker util in #1002 so you don't have to write it yourself.

@mbr

mbr commented Jun 15, 2026

Copy link
Copy Markdown
Author

Thanks. Feel free to do whatever with this, otherwise I'll check back in on this next week, as I'm traveling without access to the machine this normally runs on this week!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants