Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

trio_test_case: Merge internal version #7

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.org
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ In [1]: %autoawait trio

In [2]: import rsyscall

In [3]: t = await rsyscall.local_thread.clone()
In [3]: t = await rsyscall.local_process.clone()

In [4]: await t.stdout.write(await t.ptr("Hello world!\n"))
Hello world!
Expand Down
21 changes: 2 additions & 19 deletions docs/conceptual.org
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ In most operating system APIs, when you run a syscall,
you implicitly operate on the current process.
In rsyscall, we explicitly specify the process in which we want to run a syscall.

We refer to the processes in which we can run syscalls as "threads".[fn:1]
For rsyscall to create a new thread in which the user can run syscalls,
For rsyscall to create a new process in which the user can run syscalls,
it starts a process running the "rsyscall server stub",
which reads syscall requests from a file descriptor and sends back responses.

Expand Down Expand Up @@ -63,7 +62,7 @@ along with a limited C API,
to maximize its usability.

In Python, we treat each syscall as a method on some object,
such as Thread or FileDescriptor or ChildPid.
such as Process or FileDescriptor or ChildPid.
We've also used the type annotation features of Python 3.
This is idiomatic for Python,
but we'd love to support other languages.
Expand All @@ -77,19 +76,3 @@ like Java.

To learn more about the specifics of the API,
take a look at the documentation.

* Footnotes

[fn:1]
With most threading implementations on Linux,
such as glibc's current pthreads implementation NPTL,
each "thread" is really a separate process.
Each "thread" process is started with CLONE_THREAD
to make the collection of processes appear more like a single process.
Even before CLONE_THREAD was added, glibc's pthreads implementation was LinuxThreads,
which implements each thread with a separate process, without using CLONE_THREAD.

rsyscall's threads are likewise separate processes,
but they do not use CLONE_THREAD, so it's more like LinuxThreads;
although unlike LinuxThreads, we are not constrained by compatibility with the pthreads API;
for example, we don't need to hide the fact that each of our threads has a different pid.
6 changes: 3 additions & 3 deletions docs/misc_explanations.org
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ These are just explanations and motivating use-cases for rsyscall.
Clearly this is not POSIX, POSIX doesn't explicitly run syscalls in processes,
and we're using a lot of Linux-specific features.
Oh well, good riddance, POSIX is garbage anyway!
** Don't make syscalls in threads, make syscalls *on* threads
In other threading systems, you call syscalls in threads.
** Don't make syscalls in processs, make syscalls *on* processes
In other systems, you call syscalls in processes.

In rsyscall, you call syscalls *on* threads.
In rsyscall, you call syscalls *on* processes.
Like, as objects.
** A model for interaction with Linux that can be implemented by new languages
It's much better for languages than older models,
Expand Down
2 changes: 1 addition & 1 deletion docs/perspective_intros/high_performance.org
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
* rsyscall decouples language concurrency and kernel parallelism
You can be explicitly parallel without being concurrent.

Also, you can send your syscalls to another process to execute them while your main thread continues running,
Also, you can send your syscalls to another process to execute them while your main process continues running,
reducing the locality hit of entering the kernel, ala FlexSC.

2 changes: 1 addition & 1 deletion python/README
Original file line number Diff line number Diff line change
@@ -1 +1 @@
This is the Python version of the rsyscall thread library.
This is the Python version of the rsyscall library.
154 changes: 77 additions & 77 deletions python/rsysapps/hydra.py

Large diffs are not rendered by default.

94 changes: 47 additions & 47 deletions python/rsysapps/mail.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import trio
import socket
from rsyscall.trio_test_case import TrioTestCase
from rsyscall.thread import Thread
from rsyscall.thread import Process
from rsyscall.handle import FileDescriptor, Path
from rsyscall.command import Command
from dataclasses import dataclass
Expand All @@ -31,7 +31,7 @@ def spec(self) -> str: ...
class Maildir(MailLocation):
path: Path
@staticmethod
async def make(thr: Thread, path: Path) -> Maildir:
async def make(thr: Process, path: Path) -> Maildir:
self = Maildir(path)
await thr.mkdir(self.path)
await thr.mkdir(self.new())
Expand All @@ -43,11 +43,11 @@ def spec(self) -> str:
def new(self) -> Path:
return self.path/'new'

async def start_dovecot(nursery, thread: Thread, path: Path,
async def start_dovecot(nursery, process: Process, path: Path,
lmtp_listener: FileDescriptor, mail: MailLocation) -> Dovecot:
dovecot = await thread.environ.which("dovecot")
doveadm = await thread.environ.which("doveadm")
s6_ipcserverd = await thread.environ.which("s6-ipcserverd")
dovecot = await process.environ.which("dovecot")
doveadm = await process.environ.which("doveadm")
s6_ipcserverd = await process.environ.which("s6-ipcserverd")
config = """
protocols =
log_path = /dev/stderr
Expand All @@ -68,8 +68,8 @@ async def start_dovecot(nursery, thread: Thread, path: Path,
args = /dev/null
}
"""
config += "base_dir = " + os.fsdecode(await thread.mkdir(path/"base")) + "\n"
config += "state_dir = " + os.fsdecode(await thread.mkdir(path/"state")) + "\n"
config += "base_dir = " + os.fsdecode(await process.mkdir(path/"base")) + "\n"
config += "state_dir = " + os.fsdecode(await process.mkdir(path/"state")) + "\n"
# unfortunately, dovecot requires names for these configuration parameters, and
# doesn't accept ids. would be a nice patch to upstream...
# TODO get these with id -n{u,g} I guess?
Expand All @@ -81,18 +81,18 @@ async def start_dovecot(nursery, thread: Thread, path: Path,
# all mail we get from the socket goes to a single destination: this maildir
config += f"mail_location = {mail.spec()}\n"

config_path = await thread.spit(path/"dovecot.conf", config)
config_path = await process.spit(path/"dovecot.conf", config)
# start dovecot
dovecot_thread = await thread.clone()
dovecot_child = await dovecot_thread.exec(dovecot.args('-F', '-c', config_path))
dovecot_process = await process.clone()
dovecot_child = await dovecot_process.exec(dovecot.args('-F', '-c', config_path))
nursery.start_soon(dovecot_child.check)

# start lmtp server
lmtp_thread = await thread.clone()
lmtp_listener = lmtp_listener.move(lmtp_thread.task)
await lmtp_thread.unshare_files(going_to_exec=True)
await lmtp_thread.stdin.replace_with(lmtp_listener)
lmtp_child = await lmtp_thread.exec(s6_ipcserverd.args(
lmtp_process = await process.clone()
lmtp_listener = lmtp_listener.move(lmtp_process.task)
await lmtp_process.unshare_files(going_to_exec=True)
await lmtp_process.stdin.replace_with(lmtp_listener)
lmtp_child = await lmtp_process.exec(s6_ipcserverd.args(
doveadm.executable_path, '-c', config_path, 'exec', 'lmtp'))
nursery.start_soon(lmtp_child.check)
return Dovecot()
Expand All @@ -103,24 +103,24 @@ class Smtpd:
lmtp_listener: FileDescriptor
config_file: Path

async def start_smtpd(nursery, thread: Thread, path: Path,
async def start_smtpd(nursery, process: Process, path: Path,
smtp_listener: FileDescriptor) -> Smtpd:
smtpd = await thread.environ.which("smtpd")
smtpd_thread = await thread.clone()
smtp_listener = smtp_listener.move(smtpd_thread.task)
await smtpd_thread.unshare_files()
smtpd = await process.environ.which("smtpd")
smtpd_process = await process.clone()
smtp_listener = smtp_listener.move(smtpd_process.task)
await smtpd_process.unshare_files()

config = ""
config += 'listen on socket path "' + os.fsdecode(path/"smtpd.sock") + '"\n'
config += "table aliases file:" + os.fsdecode(await thread.spit(path/"aliases", "")) + "\n"
config += 'queue path "' + os.fsdecode(await thread.mkdir(path/"spool", mode=0o711)) + '"\n'
config += 'path chroot "' + os.fsdecode(await thread.mkdir(path/"empty")) + '"\n'
config += "table aliases file:" + os.fsdecode(await process.spit(path/"aliases", "")) + "\n"
config += 'queue path "' + os.fsdecode(await process.mkdir(path/"spool", mode=0o711)) + '"\n'
config += 'path chroot "' + os.fsdecode(await process.mkdir(path/"empty")) + '"\n'
config += "listen on localhost inherit " + str(await smtp_listener.as_argument()) + '\n'

# bind a socket in the parent
lmtp_socket = await thread.task.socket(AF.UNIX, SOCK.STREAM)
lmtp_socket = await process.task.socket(AF.UNIX, SOCK.STREAM)
lmtp_socket_path = path/"lmtp.sock"
await lmtp_socket.bind(await thread.ram.ptr(SockaddrUn(os.fsencode(lmtp_socket_path))))
await lmtp_socket.bind(await process.ram.ptr(SockaddrUn(os.fsencode(lmtp_socket_path))))
await lmtp_socket.listen(10)
config += 'action "local" lmtp "' + os.fsdecode(lmtp_socket_path) + '" user root\n'
# all mail is delivered to this single socket
Expand All @@ -137,13 +137,13 @@ async def start_smtpd(nursery, thread: Thread, path: Path,
# is mapped to an unpriv user, so this is close to the same
# security guarantee. we don't get separation between the main
# user and the queue user, though... alas.
await smtpd_thread.unshare_user(in_namespace_uid=0, in_namespace_gid=0)
await smtpd_process.unshare_user(in_namespace_uid=0, in_namespace_gid=0)
config += "queue user root\n"
config += "queue group root\n"
config += "smtp user root\n"

config_path = await thread.spit(path/"smtpd.config", config)
child = await smtpd_thread.exec(smtpd.args("-v", "-d", "-f", config_path))
config_path = await process.spit(path/"smtpd.config", config)
child = await smtpd_process.exec(smtpd.args("-v", "-d", "-f", config_path))
nursery.start_soon(child.check)

return Smtpd(
Expand All @@ -155,34 +155,34 @@ async def start_smtpd(nursery, thread: Thread, path: Path,
import rsyscall.tasks.local as local
class TestMail(TrioTestCase):
async def asyncSetUp(self) -> None:
self.thread = local.thread
self.tmpdir = await self.thread.mkdtemp("test_mail")
self.process = local.process
self.tmpdir = await self.process.mkdtemp("test_mail")
self.path = self.tmpdir.path
await update_symlink(self.thread, await self.thread.ram.ptr(self.tmpdir.parent/"test_mail.current"), self.path)
smtp_sock = await self.thread.task.socket(AF.INET, SOCK.STREAM)
await smtp_sock.bind(await self.thread.ram.ptr(SockaddrIn(3000, "127.0.0.1")))
await update_symlink(self.process, await self.process.ram.ptr(self.tmpdir.parent/"test_mail.current"), self.path)
smtp_sock = await self.process.task.socket(AF.INET, SOCK.STREAM)
await smtp_sock.bind(await self.process.ram.ptr(SockaddrIn(3000, "127.0.0.1")))
await smtp_sock.listen(10)
self.smtpd = await start_smtpd(self.nursery, self.thread, await self.thread.mkdir(self.path/"smtpd"), smtp_sock)
self.maildir = await Maildir.make(self.thread, self.path/"mail")
self.dovecot = await start_dovecot(self.nursery, self.thread, await self.thread.mkdir(self.path/"dovecot"),
self.smtpd = await start_smtpd(self.nursery, self.process, await self.process.mkdir(self.path/"smtpd"), smtp_sock)
self.maildir = await Maildir.make(self.process, self.path/"mail")
self.dovecot = await start_dovecot(self.nursery, self.process, await self.process.mkdir(self.path/"dovecot"),
self.smtpd.lmtp_listener, self.maildir)
smtpctl = await self.thread.environ.which("smtpctl")
smtpctl = await self.process.environ.which("smtpctl")
self.sendmail = Command(smtpctl.executable_path, [b'sendmail'], {'SMTPD_CONFIG_FILE': self.smtpd.config_file})
self.inty = await Inotify.make(self.thread)
self.inty = await Inotify.make(self.process)


async def asyncTearDown(self) -> None:
await self.tmpdir.cleanup()

async def send_email(self, from_: str, to: str, subject: str, msg: str) -> None:
thread = await self.thread.clone()
await thread.unshare_files()
fd = await thread.task.memfd_create(await thread.ram.ptr(Path('message')))
process = await self.process.clone()
await process.unshare_files()
fd = await process.task.memfd_create(await process.ram.ptr(Path('message')))
msg = f'From: {from_}\nSubject: {subject}\nTo: {to}\n\n' + msg
await thread.spit(fd, msg)
await process.spit(fd, msg)
await fd.lseek(0, SEEK.SET)
await thread.stdin.replace_with(fd)
child = await thread.exec(self.sendmail.args('-t'))
await process.stdin.replace_with(fd)
child = await process.exec(self.sendmail.args('-t'))
await child.check()

async def test_sendmail(self) -> None:
Expand All @@ -196,8 +196,8 @@ async def test_sendmail(self) -> None:
event = await watch.wait_until_event(IN.MOVED_TO)
if event.name is None:
raise Exception("event has no name??")
mailfd = await self.thread.task.open(await self.thread.ram.ptr(self.maildir.new()/event.name), O.RDONLY)
data = await self.thread.read_to_eof(mailfd)
mailfd = await self.process.task.open(await self.process.ram.ptr(self.maildir.new()/event.name), O.RDONLY)
data = await self.process.read_to_eof(mailfd)
message = email.message_from_bytes(data)
self.assertEqual(from_, message['From'])
self.assertEqual(to, message['To'])
Expand Down
Loading