[Bug]: Pixmap memory leak when using alignment tags (%{l}, %{c}, %{r}) in custom/script modules with tail = true
Checklist
- I have read the appropriate section in the contributing guidelines
- I believe this issue is a problem with polybar itself and not a misconfiguration on my part
- I have searched for other open and closed issues that may have already reported this problem
- I have checked the known issues page for this problem.
- I have followed the debugging guide to narrow down the problem to a minimal config.
Steps to reproduce
- Create a script that outputs alignment tags (
%{l},%{c},%{r}) usingprintfin awhile :; doloop - Configure a module with
exec = /path/to/leaks.shandtail = true - run
xrestopand observe polybar accumulating Pxms
leak-test.sh:
#!/usr/bin/env bash
i=0
while :; do
printf '%s\n' "%{l}L%{c}$i"
i=$((i + 1))
sleep 0.5
doneMinimal config
[bar/test]
width = 100%
height = 24pt
background = #1e1e2e
foreground = #cdd6f4
font-0 = monospace:size=10;2
modules-center = leaky
[module/leaky]
type = custom/script
exec = /path/to/leak-test.sh
tail = truePolybar log
notice: Parsing config file: /tmp/polybar-debug/config.ini
- config_parser: Parsing /tmp/polybar-debug/config.ini
* Environment var ${MONITOR} is undefined, using defined fallback value ""
* Loaded monitor HDMI-0 (2560x1440+0+0)
* Configured DPI = 96x96
* Bar geometry: 2560x32+0+0; Borders: 0,0,0,0
- bar: Attach X event sink
- bar: Attach signal receiver
- controller: Setup user-defined modules
notice: Loading module 'leaky' of type 'custom/script'
notice: Loaded 1 modules
* Starting application
- controller: Main thread id = 1
* Entering event loop (thread-id=1)
- bar: Create renderer
- renderer: Get TrueColor visual
* renderer: Using 32-bit TrueColor visual: 0x23
- renderer: Allocate colormap
- renderer: Allocate output window
- renderer: Allocate window pixmaps
- renderer: Allocate graphic contexts
- renderer: Allocate alignment blocks
- renderer: Allocate cairo components
- renderer: Load fonts
notice: Loaded font "JetBrainsMono Nerd Font:size=10" (name=JetBrainsMono Nerd Font, offset=2, file=/nix/store/g3jfn6fjv06zmfca4cb70vi4iwmhbbyk-nerd-fonts-jetbrains-mono-3.4.0+2.304/share/fonts/truetype/NerdFonts/JetBrainsMono/JetBrainsMonoNerdFont-Regular.ttf)
* Bar window: 0x2000001
- bar: Reconfigure window
- bar: Set window WM_NAME
- bar: Set window _NET_WM_WINDOW_TYPE
- bar: Set window _NET_WM_STATE
- bar: Set window _NET_WM_DESKTOP
- bar: Set window _NET_WM_PID
- bar: Map window
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
- bar: Draw empty bar
- bar: Setup tray manager
* Starting module/leaky
* script_runner: Invoking shell command: "/tmp/polybar-debug/leak-test.sh"
* module/leaky: Rebuilding cache
- bar: Force update
* Redrawing bar window
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
- bar: Received expose event
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=8x32+1276+0, falloff=0)
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=8x32+1276+0, falloff=0)
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=8x32+1276+0, falloff=0)
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=8x32+1276+0, falloff=0)
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=8x32+1276+0, falloff=0)
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=8x32+1276+0, falloff=0)
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=8x32+1276+0, falloff=0)
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=8x32+1276+0, falloff=0)
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=8x32+1276+0, falloff=0)
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=8x32+1276+0, falloff=0)
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=16x32+1272+0, falloff=0)
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=16x32+1272+0, falloff=0)
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=16x32+1272+0, falloff=0)
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=16x32+1272+0, falloff=0)
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=16x32+1272+0, falloff=0)
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=16x32+1272+0, falloff=0)
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=16x32+1272+0, falloff=0)
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=16x32+1272+0, falloff=0)
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=16x32+1272+0, falloff=0)
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=16x32+1272+0, falloff=0)
- background_manager: update_geometry
- background_manager: deactivating because there are no slices to observe
* module/leaky: Rebuilding cache
* Redrawing bar window
- renderer: flush(1 geom=8x32+0+0, falloff=0)
- renderer: flush(2 geom=16x32+1272+0, falloff=0)Expected behavior
When a custom/script module with tail = true outputs content containing alignment tags (%{l}, %{c}, %{r}), polybar should free the previously allocated pixmaps before allocating new ones for each update. The pixmap count (Pxms in xrestop) should remain stable over time, regardless of how many updates occur.
Actual behavior
When a custom/script module with tail = true outputs content containing alignment tags (%{l}, %{c},%{r}), polybar allocates new pixmaps on each update but never frees the previous ones. The pixmap count (Pxms in xrestop) increases indefinitely with each script output, causing continuous VRAM consumption. Over extended periods, this can exhaust GPU memory (observed growth from ~400MB to 10GB+). Restarting polybar immediately frees all accumulated pixmaps.
Note: Built-in modules using modules-left/modules-center/modules-right positioning do NOT exhibit this behavior—only alignment tags parsed from script output.
Window Manager and Version
leftwm 0.5.4
Linux Distribution
NixOS 25.11
Polybar version
› polybar -vvv
polybar 3.7.2
Features: +alsa -curl -i3 -mpd +network(libnl) +pulseaudio +xkeyboard
X extensions: +randr (+monitors) +composite +xkb +xrm +xcursor
Build type: Release
Compiler: /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-gcc-wrapper-14.3.0/bin/g++
Compiler flags: -O3 -DNDEBUG -Wall -Wextra -Wpedantic -Wdeprecated-copy-dtor -Wsuggest-override
Linker flags: -Wall -Wextra -Wpedantic -Wdeprecated-copy-dtor -Wsuggest-override -Wall -Wextra -Wpedantic -Wdeprecated-copy-dtor -Wsuggest-overrideAdditional Context / Screenshots
Source: polybar/polybar