Skip to content

unix-ffi: Fixing behavior to avoid zombie threads. - #1046

Open
klukonin wants to merge 2 commits into
micropython:masterfrom
klukonin:patch-1
Open

unix-ffi: Fixing behavior to avoid zombie threads.#1046
klukonin wants to merge 2 commits into
micropython:masterfrom
klukonin:patch-1

Conversation

@klukonin

@klukonin klukonin commented Sep 25, 2025

Copy link
Copy Markdown

This commit is fixing this issue:
#780
As discussed here https://github.com/orgs/micropython/discussions/13239

Now the pid of child thread will be stored and closed properly. Even with context manager.

@klukonin klukonin changed the title unix-ffi: Fixing behavior to avoid zombie threads unix-ffi: Fixing behavior to avoid zombie threads. Sep 25, 2025
@hodan121

Copy link
Copy Markdown

bump up

@Josverl

Josverl commented Oct 20, 2025

Copy link
Copy Markdown
Contributor

It would be good to add a test for this functionality,
that also helps with maintaining stability over time.

Other tests for the ffi module live in the micropython repo under tests/ports/unix

you could take one of the other ffi_ tests as the base, and add example code to it.

Comment thread unix-ffi/os/os/__init__.py Outdated

class _PopenStream:
def __init__(self, fd, pid, mode):
self._fd = open(f"/dev/fd/{fd}", mode)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to use builtins.open so that the returned object is a stream.

Comment thread unix-ffi/os/os/__init__.py Outdated
pid, status = waitpid(self._pid, 0)
self._exitcode = status
self._closed = True
return self._exitcode

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This _PopenStream object will need to implement read/write/etc operations, so that you can communicate with the process.

Eg prior to this patch it's possible to do:

import os
ls = os.popen("/bin/ls")
print(ls.read())
ls.close()

and that still needs to work.

@klukonin

klukonin commented Dec 2, 2025

Copy link
Copy Markdown
Author

I'll update the PR.
Thanks for the ideas, folks =)

@dpgeorge

dpgeorge commented Jul 1, 2026

Copy link
Copy Markdown
Member

@klukonin do you have any time to update this PR based on the feedback?

@klukonin

klukonin commented Jul 1, 2026

Copy link
Copy Markdown
Author

Yes. Sorry for the delay.

Previously popen() returned the pipe stream directly and the forked child
was never waited for, so it stayed around as a zombie.

Return a small wrapper object instead.  It forwards stream operations to
the pipe, so existing code such as os.popen("/bin/ls").read() keeps
working, and it waits for the child when it is closed, either explicitly
or at the end of a with statement.  As in CPython, close() returns None if
the process exited successfully and the exit status otherwise.

The child now replaces itself with the command using execvp() instead of
calling system(), which removes the extra intermediate shell process.

Fixes issue micropython#780.

Signed-off-by: Kirill Lukonin (Evil Wireless Man) <klukonin@gmail.com>
The test covers reading from and writing to a process, use as a context
manager, the exit status reported by close(), and that a process which
has been closed leaves neither a zombie nor an open file descriptor
behind.  It is run by CI, which needs the stat package to be installed
for the os package to import.

Signed-off-by: Kirill Lukonin (Evil Wireless Man) <klukonin@gmail.com>
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.

4 participants