I have a C function that's being called from Kotli...
# kotlin-native
k
I have a C function that's being called from Kotlin that prints to stdout.
Copy code
static inline void io_uring_prep_rw(int op, struct io_uring_sqe *sqe, int fd,
				    const void *addr, unsigned len,
				    __u64 offset)
{
    fprintf(stdout, "SQE pointer %p", sqe);
For the life of me I cannot get this to be printed to stdout and I don't know why. Calls to Kotlin's
println
are working fine so I suspect there's something funky going on in kotlin native modules. Does anyone have any idea why this might be happening?
m
Try flushing stdout?
Copy code
System.out.flush()
Wait, that's JVM
The same thing but native 😁
Copy code
fflush(stdout)
k
yeah
nice
When printing (e.g.
printf
), the output is put into a buffer and may not be written to the console until a newline character is displayed.
l
Which platform are you on? Android NDK will write to logcat, so it’s possible that it gets redirected on your platform.
k
My C knowledge has seriously atrophied over the years
Thank you!
This is on
linuxX64
m
Yea, it's easy to forget all this stuff 😄
I don't miss it too much 🙃
k
It's painful. It's extra painful doing C through a Kotlin cinterop lens
l
Just be glad you’re not dealing with SIGSEGs when trying to convert C data to Kotlin data.
k
I am actually trying to convince the coroutines people that
runInterruptible
should be a think on posix native with signals 😬
And I am dealing with a segfault right now. That's what I'm debugging 😭
A pointer I have in Kotlin works fine when run sequentially, but as soon as I
launch
something with a reference to that pointer my C code segfaults
The C pointer is allocated and returned from C, so I don't think it's anything to do with pinning and the like
l
Sounds about right. I’m currently dealing with a segfault that only happens when a specific view is exactly 1920x1080, and a loop runs at exactly 60 runs/second and there’s less than 2 println calls during the loop…
k
Lmao
m
Just keep the printlns 😅
l
I’ve considered adding printlns that warn future devs of what eldrich horrors await if the printlns are removed.
k
🤔 😭
Copy code
$ ./build/bin/linuxX64/debugExecutable/kotlin-io-uring.kexe 
Kotlin: Got SQE: SubmissionQueueEntry(pointer=CPointer(raw=0x7f7871fdc000)).
Kotlin: Prepping nop with SubmissionQueueEntry(pointer=CPointer(raw=0x7f7871fdc000)).
C: Prepping nop.
C: SQE pointer 0x7f7871fdc000
C: Setting SQE opcode to 0
Segmentation fault (core dumped)
It's all the same pointer I have no idea how the memory could be invalid because of a simple launch
l
Is there any chance it’s a thread local on the C side?
k
The launch should be running on the same thread
but I can try to find out.
l
What dispatcher are you using?
k
runBlocking
runblocking submits a submission queue entry, and a separate internal single threaded dispatcher polls for completions
Fails:
Copy code
public fun main(): Unit = runBlocking {
    URing(QueueDepth(2u), 0u, this).use { ring ->
        val sqe = ring.getSubmissionQueueEntry()!!
        println("Kotlin: Got SQE: $sqe.")
        launch { ring.noOp(sqe) }
    }
}
Works:
Copy code
public fun main(): Unit = runBlocking {
    URing(QueueDepth(2u), 0u, this).use { ring ->
        val sqe = ring.getSubmissionQueueEntry()!!
        println("Kotlin: Got SQE: $sqe.")
        ring.noOp(sqe)
    }
}
ring.noOp
is a suspend function that looks like this.
Copy code
public suspend fun noOp(entry: SubmissionQueueEntry) {
        return suspendCancellableCoroutine { cont ->
            val ref = StableRef.create(cont)
            println("Kotlin: Prepping nop with $entry.")
            io_uring_prep_nop(entry.pointer)
            io_uring_sqe_set_data64(entry.pointer, ref.userData)
            cont.registerIOUringCancellation(ring, entry, ref)
            io_uring_submit(ring.ptr)
        }
    }
The segfault happens in the call to
io_uring_prep_nop
only when launched.
(this is abbreviated because I am using custom continuation to perform memory cleanup, but that's not important)
l
What happens if you remove the cleanup? In theory, it should happen after the crashing code, but worth trying.
k
Let me try commenting it all out, but I don't think the cleanup is happening properly since it's segfaulting.
So I doubt it's that
m
Copy code
public fun main(): Unit = runBlocking {
    URing(QueueDepth(2u), 0u, this).use { ring ->
        val sqe = ring.getSubmissionQueueEntry()!!
        println("Kotlin: Got SQE: $sqe.")
        launch { ring.noOp(sqe) }
    }
}
Your
noOp
here might be executed after
use()
k
Ooooooooh that's a good point
l
I’ve had my fair share of ‘I doubt that would cause this crash’
m
So if it's the same
use
as the JVM one, it's most likely closing your ring
k
let me try launching and waiting for the jon
job
Martin nailed it
Now I feel supremely dumb
In all fairness these are "low level" apis which wouldn't normally be used
l
It’s easy to make mistakes when interoping with C. I’d recommend adding a check in Kotlin that will throw an exception.
k
Yes! I was going to add those but then I stumbled into this segfault and nerd sniped myself
This actually cropped up when I was implementing those checks 😂 😭
You two rock, thank you.
git reset --hard
I'm still toying with the API and how it will end up looking, but I'd like the user facing API to look something like this.
Copy code
suspend fun withIOUring(block: suspend CoroutineScope.(URing) -> Unit)
That would ensure that all coroutines launched adjacent to the URing complete before the ring is cancelled or closed.