Borg gets stuck under systemd when doing database backups #1118

Closed
opened 2025-07-10 08:54:59 +00:00 by SCjona · 10 comments

What I'm trying to do and why

I updated to Ubuntu 24.04 LTS (from 22.04 LTS) and updated borgmatic to 2.0.7 (can't tell you exactly from which version, but I'd say 1.8.10/1.8.9 as an educated guess)

After that my configuration broke. I'm using database backups and borgmatic complained that the path these were created at were excluded from the backup. After fixing that I ran into this issue. running borgmatic as root user manually works fine, but as soon as I run it as systemd service borg hangs.

Steps to reproduce

Borgmatic config:

repositories:
    - path: ssh://<censored>

patterns:
  - 'R /'
  - '+ run/borgmatic'
  - '- run'
  - '! var/cache'
  - '! var/lib/systemd/coredump'
  - '! var/lib/libvirt/images'
  - '! var/lib/docker/overlay2'
  - '! var/lib/docker/image'
  - '! var/lib/docker/buildkit'
  - '! var/lib/docker/containers'
  - '! proc'
  - '! sys'
  - '! dev'
  - '+ tmp/borgmatic*'
  - '- tmp'
  - '! lost+found'
  - '- swap.img'
  - '! var/lib/lxcfs'
  - '! var/lib/docker/volumes/service1-db-data'
  - '! var/lib/docker/volumes/service2_postgres-data'

exclude_caches: true
encryption_passphrase: <censored>

before_backup:
    - /root/update-hosts.sh # updates /etc/hosts to point database hostnames to docker container ips

sentry:
  data_source_name_url: <censored>
  monitor_slug: <censored>

postgresql_databases:
    - name: all
      hostname: service1-db
      username: <censored>
      password: <censored>
    - name: all
      hostname: service2-db
      password: <censored>
      username: <censored>

SystemD unit config (security lockdown disabled for debugging)

[Unit]
Description=borgmatic backup
Wants=network-online.target
After=network-online.target
ConditionACPower=true

[Service]
Type=oneshot

RuntimeDirectory=borgmatic
StateDirectory=borgmatic
LoadCredentialEncrypted=borgmatic.pw

Nice=19
CPUSchedulingPolicy=batch
IOSchedulingClass=best-effort
IOSchedulingPriority=7
IOWeight=100

Restart=no
LogRateLimitIntervalSec=0

ExecStartPre=sleep 1m
ExecStart=systemd-inhibit --who="borgmatic" --what="sleep:shutdown" --why="Prevent interrupting scheduled backup" /root/.local/bin/borgmatic --verbosity 2 --syslog-verbosity 1 --list --stats

Actual behavior

Attaching GDB to borg shows its stuck at an openat64 syscall with the following path: /run/borgmatic/postgresql_databases/service1-db/all

(gdb) py-list
 290        _flags_normal = flags
 291        if noatime:
 292            _flags_noatime = _flags_normal | O_('NOATIME')
 293            try:
 294                # if we have O_NOATIME, this likely will succeed if we are root or owner of file:
>295                fd = os.open(fname, _flags_noatime, dir_fd=parent_fd)
 296            except PermissionError:
 297                if _flags_noatime == _flags_normal:
 298                    # we do not have O_NOATIME, no need to try again:
 299                    raise
 300                # Was this EPERM due to the O_NOATIME flag? Try again without it:
(gdb) py-bt
Traceback (most recent call first):
  File "/usr/lib/python3/dist-packages/borg/helpers/fs.py", line 295, in os_open
    fd = os.open(fname, _flags_noatime, dir_fd=parent_fd)
  File "/usr/lib/python3/dist-packages/borg/archive.py", line 268, in OsOpen
    fd = os_open(path=path, parent_fd=parent_fd, name=name, flags=flags, noatime=noatime)
  File "/usr/lib/python3.12/contextlib.py", line 137, in __enter__
    return next(self.gen)
  File "/usr/lib/python3/dist-packages/borg/archive.py", line 1414, in process_file
    with OsOpen(path=path, parent_fd=parent_fd, name=name, flags=flags, noatime=True) as fd:
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 717, in _process_any
    return fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st,
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 793, in _rec_walk
    status = self._process_any(path=path, parent_fd=parent_fd, name=name, st=st, fso=fso, cache=cache,
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 830, in _rec_walk
    self._rec_walk(
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 830, in _rec_walk
    self._rec_walk(
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line ?, in create_inner
    (failed to get frame line number)
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 682, in do_create
    create_inner(archive, cache, fso)
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 190, in wrapper
    return method(self, args, repository=repository, **kwargs)
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 5321, in run
    return set_ec(func(args))
  File "/usr/lib/python3/dist-packages/borg/archiver.py", line 5401, in main
    exit_code = archiver.run(args)
  File "/usr/bin/borg", line 33, in <module>
    sys.exit(load_entry_point('borgbackup==1.2.8', 'console_scripts', 'borg')())

The database dump processes meanwhile died and are just zombie processes (they execute before it gets stuck so it looks like borg tries to do these backups twice)

log excerpt:

<long file list (output of --list)>
A /run/borgmatic/postgresql_databases/service1-db/all
A /run/borgmatic/postgresql_databases/service2-db/all
<long file list (output of --list)>
A /etc/gdb/gdbinit
M /etc/ld.so.cache
x /proc
====<< it gets stuck here when running in systemd >>===
Remote: check_free_space: required bytes 587207848, free bytes 537689325568
security: saving state for <censored> to /root/.config/borg/security/<censored>
security: current location   ssh://<censored>
security: key type           <censored>
security: manifest timestamp <censored>
Remote: Verified integrity of <censored>
Archive authentication DISABLED.
TAM-verified archive

Expected behavior

It does not get stuck and works as expected

Other notes / implementation ideas

No response

borgmatic version

2.0.7

borgmatic installation method

apt-get install borgbackup && pipx install borgmatic

Borg version

1.2.8

Python version

Python 3.12.3

Database version (if applicable)

server: postgres:16 (docker container) 16.2-1.pgdg120+2 | client: psql (PostgreSQL) 16.9 (Ubuntu 16.9-1.pgdg24.04+1)

Operating system and version

Ubuntu 24.04.2 LTS

### What I'm trying to do and why I updated to Ubuntu 24.04 LTS (from 22.04 LTS) and updated borgmatic to 2.0.7 (can't tell you exactly from which version, but I'd say 1.8.10/1.8.9 as an educated guess) After that my configuration broke. I'm using database backups and borgmatic complained that the path these were created at were excluded from the backup. After fixing that I ran into this issue. running borgmatic as root user manually works fine, but as soon as I run it as systemd service borg hangs. ### Steps to reproduce Borgmatic config: ```yaml repositories: - path: ssh://<censored> patterns: - 'R /' - '+ run/borgmatic' - '- run' - '! var/cache' - '! var/lib/systemd/coredump' - '! var/lib/libvirt/images' - '! var/lib/docker/overlay2' - '! var/lib/docker/image' - '! var/lib/docker/buildkit' - '! var/lib/docker/containers' - '! proc' - '! sys' - '! dev' - '+ tmp/borgmatic*' - '- tmp' - '! lost+found' - '- swap.img' - '! var/lib/lxcfs' - '! var/lib/docker/volumes/service1-db-data' - '! var/lib/docker/volumes/service2_postgres-data' exclude_caches: true encryption_passphrase: <censored> before_backup: - /root/update-hosts.sh # updates /etc/hosts to point database hostnames to docker container ips sentry: data_source_name_url: <censored> monitor_slug: <censored> postgresql_databases: - name: all hostname: service1-db username: <censored> password: <censored> - name: all hostname: service2-db password: <censored> username: <censored> ``` SystemD unit config (security lockdown disabled for debugging) ``` [Unit] Description=borgmatic backup Wants=network-online.target After=network-online.target ConditionACPower=true [Service] Type=oneshot RuntimeDirectory=borgmatic StateDirectory=borgmatic LoadCredentialEncrypted=borgmatic.pw Nice=19 CPUSchedulingPolicy=batch IOSchedulingClass=best-effort IOSchedulingPriority=7 IOWeight=100 Restart=no LogRateLimitIntervalSec=0 ExecStartPre=sleep 1m ExecStart=systemd-inhibit --who="borgmatic" --what="sleep:shutdown" --why="Prevent interrupting scheduled backup" /root/.local/bin/borgmatic --verbosity 2 --syslog-verbosity 1 --list --stats ``` ### Actual behavior Attaching GDB to borg shows its stuck at an openat64 syscall with the following path: `/run/borgmatic/postgresql_databases/service1-db/all` ``` (gdb) py-list 290 _flags_normal = flags 291 if noatime: 292 _flags_noatime = _flags_normal | O_('NOATIME') 293 try: 294 # if we have O_NOATIME, this likely will succeed if we are root or owner of file: >295 fd = os.open(fname, _flags_noatime, dir_fd=parent_fd) 296 except PermissionError: 297 if _flags_noatime == _flags_normal: 298 # we do not have O_NOATIME, no need to try again: 299 raise 300 # Was this EPERM due to the O_NOATIME flag? Try again without it: (gdb) py-bt Traceback (most recent call first): File "/usr/lib/python3/dist-packages/borg/helpers/fs.py", line 295, in os_open fd = os.open(fname, _flags_noatime, dir_fd=parent_fd) File "/usr/lib/python3/dist-packages/borg/archive.py", line 268, in OsOpen fd = os_open(path=path, parent_fd=parent_fd, name=name, flags=flags, noatime=noatime) File "/usr/lib/python3.12/contextlib.py", line 137, in __enter__ return next(self.gen) File "/usr/lib/python3/dist-packages/borg/archive.py", line 1414, in process_file with OsOpen(path=path, parent_fd=parent_fd, name=name, flags=flags, noatime=True) as fd: File "/usr/lib/python3/dist-packages/borg/archiver.py", line 717, in _process_any return fso.process_file(path=path, parent_fd=parent_fd, name=name, st=st, File "/usr/lib/python3/dist-packages/borg/archiver.py", line 793, in _rec_walk status = self._process_any(path=path, parent_fd=parent_fd, name=name, st=st, fso=fso, cache=cache, File "/usr/lib/python3/dist-packages/borg/archiver.py", line 830, in _rec_walk self._rec_walk( File "/usr/lib/python3/dist-packages/borg/archiver.py", line 830, in _rec_walk self._rec_walk( File "/usr/lib/python3/dist-packages/borg/archiver.py", line ?, in create_inner (failed to get frame line number) File "/usr/lib/python3/dist-packages/borg/archiver.py", line 682, in do_create create_inner(archive, cache, fso) File "/usr/lib/python3/dist-packages/borg/archiver.py", line 190, in wrapper return method(self, args, repository=repository, **kwargs) File "/usr/lib/python3/dist-packages/borg/archiver.py", line 5321, in run return set_ec(func(args)) File "/usr/lib/python3/dist-packages/borg/archiver.py", line 5401, in main exit_code = archiver.run(args) File "/usr/bin/borg", line 33, in <module> sys.exit(load_entry_point('borgbackup==1.2.8', 'console_scripts', 'borg')()) ``` The database dump processes meanwhile died and are just zombie processes (they execute before it gets stuck so it looks like borg tries to do these backups twice) log excerpt: ``` <long file list (output of --list)> A /run/borgmatic/postgresql_databases/service1-db/all A /run/borgmatic/postgresql_databases/service2-db/all <long file list (output of --list)> A /etc/gdb/gdbinit M /etc/ld.so.cache x /proc ====<< it gets stuck here when running in systemd >>=== Remote: check_free_space: required bytes 587207848, free bytes 537689325568 security: saving state for <censored> to /root/.config/borg/security/<censored> security: current location ssh://<censored> security: key type <censored> security: manifest timestamp <censored> Remote: Verified integrity of <censored> Archive authentication DISABLED. TAM-verified archive ``` ### Expected behavior It does not get stuck and works as expected ### Other notes / implementation ideas _No response_ ### borgmatic version 2.0.7 ### borgmatic installation method apt-get install borgbackup && pipx install borgmatic ### Borg version 1.2.8 ### Python version Python 3.12.3 ### Database version (if applicable) server: postgres:16 (docker container) 16.2-1.pgdg120+2 | client: psql (PostgreSQL) 16.9 (Ubuntu 16.9-1.pgdg24.04+1) ### Operating system and version Ubuntu 24.04.2 LTS
Owner

Thanks for the detailed ticket and debugging. Based on your description though, I'm not immediately sure what might be causing this. Here are some initial thoughts:

The database dump processes meanwhile died and are just zombie processes (they execute before it gets stuck so it looks like borg tries to do these backups twice)

Do you have any indication about how/why the dump processes died? Anything in borgmatic's logs or PostgreSQL logs? It does make sense to me that Borg would hang on reading the database pipe if nothing is writing to it, but I don't know why the dump processes might've died—and only when running under systemd. Or did they maybe just finish executing successfully, but Borg is still expecting them to be writing output?

When you say that Borg tries to do these backups twice, what do you mean by that? Do you see for instance /run/borgmatic/postgresql_databases/service1-db/all twice in Borg's file list? And what about when borgmatic prints out its own list of patterns? (See the lines that come right after the log line Writing patterns to ...)

One thing you could try is commenting out the following lines from your configured patterns:

  - '+ run/borgmatic'
  - '- run'

They shouldn't cause problems, and if they did I'd expect them to also cause problems when running borgmatic manually. But I'm wondering if maybe there's an interaction with borgmatic's runtime directory (where database pipes are located). And that runtime directory can differ when running borgmatic under systemd.

Speaking of which, can you show me the output of borgmatic's log line Using runtime directory ..., both when running borgmatic manually and when running it under systemd? Thanks!

Thanks for the detailed ticket and debugging. Based on your description though, I'm not immediately sure what might be causing this. Here are some initial thoughts: > The database dump processes meanwhile died and are just zombie processes (they execute before it gets stuck so it looks like borg tries to do these backups twice) Do you have any indication about how/why the dump processes died? Anything in borgmatic's logs or PostgreSQL logs? It does make sense to me that Borg would hang on reading the database pipe if nothing is writing to it, but I don't know why the dump processes might've died—and only when running under systemd. Or did they maybe just finish executing successfully, but Borg is still expecting them to be writing output? When you say that Borg tries to do these backups twice, what do you mean by that? Do you see for instance `/run/borgmatic/postgresql_databases/service1-db/all` twice in Borg's file list? And what about when borgmatic prints out its own list of patterns? (See the lines that come right after the log line `Writing patterns to ...`) One thing you could try is commenting out the following lines from your configured `patterns`: ``` - '+ run/borgmatic' - '- run' ``` They *shouldn't* cause problems, and if they did I'd expect them to also cause problems when running borgmatic manually. But I'm wondering if maybe there's an interaction with borgmatic's runtime directory (where database pipes are located). And that runtime directory can differ when running borgmatic under systemd. Speaking of which, can you show me the output of borgmatic's log line `Using runtime directory ...`, both when running borgmatic manually and when running it under systemd? Thanks!
Author

Do you have any indication about how/why the dump processes died?

They execute successfully. The parent process just does not close its fd's so they stay zombie processes temporarily.

When you say that Borg tries to do these backups twice, what do you mean by that?

A /run/borgmatic/postgresql_databases/service1-db/all is only written once in the output log. It probably does not try to backup it twice, but tries to access the file twice, once to backup and once after the backup for whatever reason (and that part gets stuck)

Maybe this helps:

  1. Backup starts
  2. Database dump processes start (/bin/sh -c pg_dumpall .... > /run/./borgmatic/postgresql_databases/service{1,2}/all not doing anything yet
  3. Borg outputs A /run/borgmatic/postgresql_databases/service1-db/all
  4. The database dump gets executed (pg_dumpall is executed)
  5. The /bin/sh -c pg_dumpall ... > ... process exits (is now a zombie). This also happens running under root user.
  6. Borg continues
  7. Borg finishes last entry in backup (x /proc log line)
  8. Borg tries to open /run/borgmatic/postgresql_databases/service1-db/all again for some reason and gets stuck there. This is the point of the GDB stacktrace I provided

One thing you could try is commenting out the following lines from your configured patterns

Sadly this does not help

These are the patterns written by borgmatic

R /
+ run/borgmatic
- run
! var/cache
! var/lib/systemd/coredump
! var/lib/libvirt/images
! var/lib/docker/overlay2
! var/lib/docker/image
! var/lib/docker/buildkit
! var/lib/docker/containers
! proc
! sys
! dev
+ tmp/borgmatic*
- tmp
! lost+found
- swap.img
! var/lib/lxcfs
! var/lib/docker/volumes/service1-db-data
! var/lib/docker/volumes/service2_postgres-data
R /run/./borgmatic/bootstrap
R /run/./borgmatic/postgresql_databases
! fm:/usr/lib/systemd/system/multipath-tools-boot.service
! fm:/usr/lib/systemd/system/sudo.service
! fm:/usr/lib/systemd/system/screen-cleanup.service
! fm:/usr/lib/systemd/system/cryptdisks-early.service
! fm:/usr/lib/systemd/system/cryptdisks.service
! fm:/usr/lib/systemd/system/hwclock.service
! fm:/usr/lib/systemd/system/x11-common.service
! fm:/snap/core20/2599/dev/null
! fm:/snap/core20/2599/dev/random
! fm:/snap/core20/2599/dev/urandom
! fm:/snap/core20/2599/dev/zero
! fm:/snap/core20/2599/usr/lib/systemd/system/cryptdisks-early.service
! fm:/snap/core20/2599/usr/lib/systemd/system/cryptdisks.service
! fm:/snap/core20/2599/usr/lib/systemd/system/hwclock.service
! fm:/snap/core20/2599/usr/lib/systemd/system/rc.service
! fm:/snap/core20/2599/usr/lib/systemd/system/rcS.service
! fm:/snap/core20/2599/usr/lib/systemd/system/sudo.service
! fm:/snap/core20/2599/usr/lib/systemd/system/x11-common.service
! fm:/snap/core20/2582/dev/null
! fm:/snap/core20/2582/dev/random
! fm:/snap/core20/2582/dev/urandom
! fm:/snap/core20/2582/dev/zero
! fm:/snap/core20/2582/usr/lib/systemd/system/cryptdisks-early.service
! fm:/snap/core20/2582/usr/lib/systemd/system/cryptdisks.service
! fm:/snap/core20/2582/usr/lib/systemd/system/hwclock.service
! fm:/snap/core20/2582/usr/lib/systemd/system/rc.service
! fm:/snap/core20/2582/usr/lib/systemd/system/rcS.service
! fm:/snap/core20/2582/usr/lib/systemd/system/sudo.service
! fm:/snap/core20/2582/usr/lib/systemd/system/x11-common.service
! fm:/var/lib/docker/volumes/backingFsBlockDev
! fm:/etc/systemd/system/ModemManager
! fm:/etc/systemd/system/sudo.service
! fm:/etc/systemd/system/fwupd-refresh.timer
! fm:/etc/systemd/system-generators/systemd-gpt-auto-generator

SystemD: Using runtime directory /run/borgmatic
root user: Using runtime directory /tmp/borgmatic-lnuavy8n/borgmatic

> Do you have any indication about how/why the dump processes died? They execute successfully. The parent process just does not close its fd's so they stay zombie processes temporarily. > When you say that Borg tries to do these backups twice, what do you mean by that? `A /run/borgmatic/postgresql_databases/service1-db/all` is only written once in the output log. It probably does not try to backup it twice, but tries to access the file twice, once to backup and once after the backup for whatever reason (and that part gets stuck) Maybe this helps: 1. Backup starts 2. Database dump processes start (`/bin/sh -c pg_dumpall .... > /run/./borgmatic/postgresql_databases/service{1,2}/all` not doing anything yet 3. Borg outputs `A /run/borgmatic/postgresql_databases/service1-db/all` 4. The database dump gets executed (`pg_dumpall` is executed) 5. The `/bin/sh -c pg_dumpall ... > ...` process exits (is now a zombie). This also happens running under root user. 6. Borg continues 7. Borg finishes last entry in backup (`x /proc` log line) 8. Borg tries to open `/run/borgmatic/postgresql_databases/service1-db/all` again for some reason and gets stuck there. This is the point of the GDB stacktrace I provided > One thing you could try is commenting out the following lines from your configured patterns Sadly this does not help These are the patterns written by borgmatic ``` R / + run/borgmatic - run ! var/cache ! var/lib/systemd/coredump ! var/lib/libvirt/images ! var/lib/docker/overlay2 ! var/lib/docker/image ! var/lib/docker/buildkit ! var/lib/docker/containers ! proc ! sys ! dev + tmp/borgmatic* - tmp ! lost+found - swap.img ! var/lib/lxcfs ! var/lib/docker/volumes/service1-db-data ! var/lib/docker/volumes/service2_postgres-data R /run/./borgmatic/bootstrap R /run/./borgmatic/postgresql_databases ! fm:/usr/lib/systemd/system/multipath-tools-boot.service ! fm:/usr/lib/systemd/system/sudo.service ! fm:/usr/lib/systemd/system/screen-cleanup.service ! fm:/usr/lib/systemd/system/cryptdisks-early.service ! fm:/usr/lib/systemd/system/cryptdisks.service ! fm:/usr/lib/systemd/system/hwclock.service ! fm:/usr/lib/systemd/system/x11-common.service ! fm:/snap/core20/2599/dev/null ! fm:/snap/core20/2599/dev/random ! fm:/snap/core20/2599/dev/urandom ! fm:/snap/core20/2599/dev/zero ! fm:/snap/core20/2599/usr/lib/systemd/system/cryptdisks-early.service ! fm:/snap/core20/2599/usr/lib/systemd/system/cryptdisks.service ! fm:/snap/core20/2599/usr/lib/systemd/system/hwclock.service ! fm:/snap/core20/2599/usr/lib/systemd/system/rc.service ! fm:/snap/core20/2599/usr/lib/systemd/system/rcS.service ! fm:/snap/core20/2599/usr/lib/systemd/system/sudo.service ! fm:/snap/core20/2599/usr/lib/systemd/system/x11-common.service ! fm:/snap/core20/2582/dev/null ! fm:/snap/core20/2582/dev/random ! fm:/snap/core20/2582/dev/urandom ! fm:/snap/core20/2582/dev/zero ! fm:/snap/core20/2582/usr/lib/systemd/system/cryptdisks-early.service ! fm:/snap/core20/2582/usr/lib/systemd/system/cryptdisks.service ! fm:/snap/core20/2582/usr/lib/systemd/system/hwclock.service ! fm:/snap/core20/2582/usr/lib/systemd/system/rc.service ! fm:/snap/core20/2582/usr/lib/systemd/system/rcS.service ! fm:/snap/core20/2582/usr/lib/systemd/system/sudo.service ! fm:/snap/core20/2582/usr/lib/systemd/system/x11-common.service ! fm:/var/lib/docker/volumes/backingFsBlockDev ! fm:/etc/systemd/system/ModemManager ! fm:/etc/systemd/system/sudo.service ! fm:/etc/systemd/system/fwupd-refresh.timer ! fm:/etc/systemd/system-generators/systemd-gpt-auto-generator ``` SystemD: `Using runtime directory /run/borgmatic` root user: `Using runtime directory /tmp/borgmatic-lnuavy8n/borgmatic`
Author

I just set up a fresh debian 12 VM with postgresql, borgbackup & borgmatic with my config. there it even hangs when running under the root user, but now the runtime directory for the root user is /run/user/0/borgmatic. I can provide you with the VM disk image if you want

I just set up a fresh debian 12 VM with postgresql, borgbackup & borgmatic with my config. there it even hangs when running under the root user, but now the runtime directory for the root user is `/run/user/0/borgmatic`. I can provide you with the VM disk image if you want
Author

I played with the patterns a bit. Seems like this only happens when the pattern list includes one of these:

  • R /
  • R /run

if I switch to a whitelist approach by explicitly listing /etc, /home, /root, /usr and /var it works fine again

My theory:
Since borgmatic appends this to my patterns:

R /run/./borgmatic/bootstrap
R /run/./borgmatic/postgresql_databases

the /run/./borgmatic/postgresql_databases directory is scanned twice, which causes a hang on the second scan.

When I patch borgmatic/hooks/data_source/postgresql.py function dump_data_sources and remove this part

    if not dry_run:
        patterns.append(
            borgmatic.borg.pattern.Pattern(
                os.path.join(borgmatic_runtime_directory, 'postgresql_databases'),
                source=borgmatic.borg.pattern.Pattern_source.HOOK,
            )
        )

borg no longer hangs

I played with the patterns a bit. Seems like this only happens when the pattern list includes one of these: - `R /` - `R /run` if I switch to a whitelist approach by explicitly listing /etc, /home, /root, /usr and /var it works fine again My theory: Since borgmatic appends this to my patterns: ``` R /run/./borgmatic/bootstrap R /run/./borgmatic/postgresql_databases ``` the `/run/./borgmatic/postgresql_databases` directory is scanned twice, which causes a hang on the second scan. When I patch `borgmatic/hooks/data_source/postgresql.py` function `dump_data_sources` and remove this part ```py if not dry_run: patterns.append( borgmatic.borg.pattern.Pattern( os.path.join(borgmatic_runtime_directory, 'postgresql_databases'), source=borgmatic.borg.pattern.Pattern_source.HOOK, ) ) ``` borg no longer hangs
Owner

the /run/./borgmatic/postgresql_databases directory is scanned twice, which causes a hang on the second scan.

I think your theory is likely right.

SystemD: Using runtime directory /run/borgmatic
root user: Using runtime directory /tmp/borgmatic-lnuavy8n/borgmatic

And this explains the difference between running manually and running under systemd—since borgmatic appends /run/borgmatic only under systemd, that's when it duplicates the existing + run/borgmatic pattern and causes the hang. And with your VM scenario, my guess is that /run/user/0/borgmatic is getting duplicated by R run.

As for a solution, first let me see if I can get a repro case setup. I'll let you know if I can't and whether a VM image would be helpful. But assuming that I can repro it, I'll look into why the existing deduplication code (deduplicate_patterns() in borgmatic/actions/pattern.py)—which is designed to prevent this sort of problem—isn't successfully deduplicating the overlapping paths.

> the /run/./borgmatic/postgresql_databases directory is scanned twice, which causes a hang on the second scan. I think your theory is likely right. > SystemD: `Using runtime directory /run/borgmatic` > root user: `Using runtime directory /tmp/borgmatic-lnuavy8n/borgmatic` And this explains the difference between running manually and running under systemd—since borgmatic appends `/run/borgmatic` only under systemd, that's when it duplicates the existing `+ run/borgmatic` pattern and causes the hang. And with your VM scenario, my guess is that `/run/user/0/borgmatic` is getting duplicated by `R run`. As for a solution, first let me see if I can get a repro case setup. I'll let you know if I can't and whether a VM image would be helpful. But assuming that I can repro it, I'll look into why the existing deduplication code (`deduplicate_patterns()` in `borgmatic/actions/pattern.py`)—which is designed to prevent this sort of problem—isn't successfully deduplicating the overlapping paths.
Owner

I think I have a repro—and also an idea about what's going on. The existing deduplication logic only deduplicates root patterns (patterns prefixed with R). And because you've got implicit duplication across one non-root pattern (+ run/borgmatic) and one root pattern (/run/./borgmatic/postgresql_databases), no deduplication occurs, both patterns get passed to Borg, Borg tries to read each named pipe twice, and a hang occurs.

Now, the question is what to do about this!

EDIT: Looks like this initial assessment might be wrong... Still looking into this.

I think I have a repro—and also an idea about what's going on. The existing deduplication logic only deduplicates *root patterns* (patterns prefixed with `R`). And because you've got implicit duplication across one non-root pattern (`+ run/borgmatic`) and one root pattern (`/run/./borgmatic/postgresql_databases`), no deduplication occurs, both patterns get passed to Borg, Borg tries to read each named pipe twice, and a hang occurs. Now, the question is what to do about this! EDIT: Looks like this initial assessment might be wrong... Still looking into this.
Owner

Okay, it looks like that initial assessment was wrong. What's actually happening is that two root patterns are not getting de-duped: R / and R /run/./borgmatic/postgresql_databases ... even though / logically contains /run/./borgmatic/postgresql_databases and therefore only one of them should get passed to Borg (to prevent hangs).

And the reason they're not getting de-duped, at least on my system, is that they're on different filesystem devices. / is on my standard root filesystem (ext4 here), while /run/... is on tmpfs. I imagine your system is similar. Anyway, there's code in deduplicate_patterns() that intentionally skips de-duping for patterns on different filesystems. That's because theone_file_system option might be true, in which case that would tell Borg not to cross filesystems implicitly—and therefore both patterns would still need to be included in order for Borg to back them up.

But one_file_system isn't set to true in your case (or in my repro), and so perhaps this code should actually look at the value of that option to determine whether a pattern's filesystem device impacts its deduplication!

Phew!

Okay, it looks like that initial assessment was wrong. What's actually happening is that two *root patterns* are not getting de-duped: `R /` and `R /run/./borgmatic/postgresql_databases` ... even though `/` logically contains `/run/./borgmatic/postgresql_databases` and therefore only one of them should get passed to Borg (to prevent hangs). And the reason they're not getting de-duped, at least on my system, is that they're on different filesystem devices. `/` is on my standard root filesystem (ext4 here), while `/run/...` is on `tmpfs`. I imagine your system is similar. Anyway, there's code in `deduplicate_patterns()` that intentionally skips de-duping for patterns on different filesystems. That's because the`one_file_system` option *might* be `true`, in which case that would tell Borg *not* to cross filesystems implicitly—and therefore both patterns would still need to be included in order for Borg to back them up. But `one_file_system` *isn't* set to `true` in your case (or in my repro), and so perhaps this code should actually look at the value of that option to determine whether a pattern's filesystem device impacts its deduplication! Phew!
Author

And the reason they're not getting de-duped, at least on my system, is that they're on different filesystem devices. / is on my standard root filesystem (ext4 here), while /run/... is on tmpfs. I imagine your system is similar.

Yes this also explains why it works fine running as root user on the ubuntu system I originally had the issue on. /tmp is not an extra mount so its part of the root file system, while /run is a tmpfs.

> And the reason they're not getting de-duped, at least on my system, is that they're on different filesystem devices. / is on my standard root filesystem (ext4 here), while /run/... is on tmpfs. I imagine your system is similar. Yes this also explains why it works fine running as root user on the ubuntu system I originally had the issue on. `/tmp` is not an extra mount so its part of the root file system, while `/run` is a tmpfs.
Owner

Makes sense! So this should be fixed in main now and will be part of the next release. Thanks again for all the details here.

Makes sense! So this should be fixed in main now and will be part of the next release. Thanks again for all the details here.
witten 2025-07-15 20:05:59 +00:00
  • closed this issue
  • added the
    bug
    label
Owner

Released in borgmatic 2.0.8!

Released in borgmatic 2.0.8!
Sign in to join this conversation.
No milestone
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
borgmatic-collective/borgmatic#1118
No description provided.