Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@ppannuto
Copy link
Member

@ppannuto ppannuto commented Jul 28, 2023

Pull Request Overview

Following the discussion at TockWorld6, this describes the proposed Yield-WaitFor and provides a (untested) rough implementation of how the kernel could easily implement it.

For ease of viewing, this draft PR edits TRD 104 directly so it can be seen as a diff. A final PR would follow the proper, full TRD process.

The primary motivation to move this functionality from a userspace yield_for into a specialized system call is to simplify correctness for userspace applications. Userspace upcall handlers do not have to worry about reentrancy if the kernel guarantees that exactly one and only one specific one of userspace's choosing will be called. It becomes an opt-in synchronous API for userspace without reducing the fundamental asynchronous design of Tock.

Testing Strategy

Compiling (and no more!).

TODO or Help Wanted

This is currently designed and architected as a minimal-impact change. In particular, if you want the actual status or return value from the upcall, you still need to have supplied a callback function. If (e.g., often in the case of prints) you don't care about the return success/failure, then you can just leave the default Null Upcall in place and this will do what you want.

The implementation is not intended as final. Rather, it's just trying to demonstrate how this can be a very lightweight change in the kernel (indeed, one should not write careful code while also trying to participate in a meeting 😅 ).

Documentation Updated

  • Updated the relevant files in /docs, or no updates are required.

Formatting

  • Ran make prepush.

Rendered

@bradjc
Copy link
Contributor

bradjc commented Jul 31, 2023

I think we want something like this. This yield-for makes mixing async and sync code in userspace easier. Currently, it is fairly easy to write all async or all sync code in userspace, but when mixing them async callbacks can lead to unexpected call chains when using yield_for() in userspace. With this yield for syscall, sync code knows no other callbacks will happen.

The decision to make is whether to go with this lightweight version, or something more integrated (or something else).

Instead (or in addition to), we could add yield-for-blocking, which would remove the upcall entirely, and instead pass the upcall arguments to userspace via the return arguments of yield-for-blocking.

Advantages of yield-for-blocking:

  1. Userspace sync code is easy to write:

    allow();
    command();
    ret = yield_for_blocking(UPCALL_ID, &arg1, &arg2, &arg3);
    unallow();

    instead of:

    void callback(e, arg1, arg2, arg3) {
        arg1=arg1;
        arg2=arg2;
        arg3=arg3;
    }
    
    allow();
    subscribe(callback);
    command();
    yield_for(UPCALLID);
    unallow();
  2. Eliminates the context switches for subscribe and the upcall.

Disadvantages:

  1. Semantic confusion: schedule_upcall() in a capsule may not, actually, schedule an upcall any more. Instead the "upcall" happens as the return to a yield.
  2. If userspace does call subscribe, but then only uses yield-for-blocking, that upcall would never get called, which is odd. But with a mix of yield-for-blocking and yield, the upcall would get called sometimes. This inconsistency complicates the interface.

Having articulated this, I see the appeal of yield-for. I think Pat pointed out that the complexity in userspace code is fairly easy to hide in a library layer, so that driver code would look like the yield-for-blocking case. Perhaps the prudent move is to address the complexity of writing sync and async code in userspace with yield-for, and not try to introduce blocking semantics before we know the aysnc version is insufficient.

@ppannuto
Copy link
Member Author

A quick note, UPCALL_ID is made up of driver_number and subscribe_number in practice, so it has to be two arguments. The upcall signature is currently three integer arguments and a user data pointer; while the blocking call can eliminate the last argument, as the pointer can just be local to the userland callsite, one of the arguments would have to overwrite one passed in. That makes this pseudocode more awkward:

allow();
command();
// Can't do this:   ret = yield_for_blocking(UPCALL_ID, &arg1, &arg2, &arg3);
int subscribe_num_and_arg1 = SUBSCRIBE_NUMBER;
ret = yield_for_blocking(DRIVER_NUMBER, &subscribe_num_and_arg1, &arg2, &arg3);
unallow();

There are probably other games that could be played to ease this, but it's all more complex than the lightweight change I think we should start with as-proposed here.

@bradjc
Copy link
Contributor

bradjc commented Jul 31, 2023

allow();
command();
// Can't do this:   ret = yield_for_blocking(UPCALL_ID, &arg1, &arg2, &arg3);
int subscribe_num_and_arg1 = SUBSCRIBE_NUMBER;
ret = yield_for_blocking(DRIVER_NUMBER, &subscribe_num_and_arg1, &arg2, &arg3);
unallow();

But the arg1 arg2 arg3 are going back up (kernel to userspace) and driver num and subscribe num are going down (userspace to kernel).

@ppannuto
Copy link
Member Author

Oh, you were thinking some more library magic here? I guess that works so long as the lower layer does the remapping and the pointer writing. I don't think there is any way to pass all the pointers for arg1-arg3 in the syscall and have the kernel do the write, but that's not necessary.... I think the below would work?

// libtock.c
int yield_for_blocking(struct upcall_id, &arg1, &arg2, &arg3) {
    register uint32_t r0 __asm__ ("r0") = YIELD_FOR_BLOCKING_ID;
    register uint32_t r1 __asm__ ("r1") = upcall_id.drv_num;
    register uint32_t r2 __asm__ ("r2") = upcall_id.sub_num;
    register int retval __asm__ ("r0");
    register int rv1 __asm__ ("r1");
    register int rv2 __asm__ ("r2");
    register int rv3 __asm__ ("r3");
    __asm__ volatile (
    "svc 0"
    : "=r" (retval), "=r" (rv1), "=r" (rv2), "=r" (rv3)
    : "r" (r0), "r" (r1), "r" (r2)
    : "memory");

    if (retval & YIELD_FOR_ACTUALLY_YIELDED_FLAG_MASK) {
        *arg1 = rv1;
        *arg2 = rv2;
        *arg3 = rv3;
    }
    return retval;
}

@bradjc
Copy link
Contributor

bradjc commented Jul 31, 2023

Yes that is what I was imagining.

@brghena
Copy link
Contributor

brghena commented Jul 31, 2023

The C wrapper could just take five arguments though instead of a struct:

int yield_for_blocking(driver_num, subscribe_num, &arg1, &arg2, &arg3) {

@ppannuto
Copy link
Member Author

True, though, since we're size-optimizing, if that function didn't get inlined everywhere, adding the fifth argument would require spilling to the stack [assuming stuct upcall_id were passed by ref, unlike the implicit copy I wrote above..] — for something low-level and apparently on the hot path like this, I'd push to limit to four args generally.

@hudson-ayers
Copy link
Contributor

True, though, since we're size-optimizing, if that function didn't get inlined everywhere, adding the fifth argument would require spilling to the stack [assuming stuct upcall_id were passed by ref, unlike the implicit copy I wrote above..] — for something low-level and apparently on the hot path like this, I'd push to limit to four args generally.

This function seems like something that should get inlined everywhere, provided that it is being built with reasonable optimization settings, which should be the case for any project particularly concerned about size. So I would advocate for the five argument function definition, though I don't feel super strongly about it. If we used your approach, wouldn't we need to pass a pointer to upcall_id in order to not spill to the stack?

That said, I think that the disadvantages Brad described are valid:

Disadvantages:

Semantic confusion: schedule_upcall() in a capsule may not, actually, schedule an upcall any more. Instead the "upcall" happens as the return to a yield.
If userspace does call subscribe, but then only uses yield-for-blocking, that upcall would never get called, which is odd. But with a mix of yield-for-blocking and yield, the upcall would get called sometimes. This inconsistency complicates the interface.

These are both disadvantages that do not exist with blocking_command() as Alyssa described it at TockWorld.

yield_for(), in cases where the return value matters, does not remove any system calls compared to the current status quo, or the need for a callback function. It just allows userspace to ensure that only one callback will ever be called. Note that libtock-rs today only even allows one outstanding callback, so this seems to be of very limited usefulness in libtock-rs.

yield_for_blocking(), as Brad described, removes the context switch for subscribe (and unsubscribe in libtock-rs), as well as the need to implement a callback. But it comes with the two disadvantages that Brad mentioned, and still requires one more context switch than blocking_command(). The potential for mixing of functions that call yield_for_blocking() and subscribe() takes away a lot of the purported advantage here of "making it easier to simplify correctness for userspace apps", since there would be a new class of bug if apps mix async and sync driver methods that use the same callbacks.

@ppannuto
Copy link
Member Author

ppannuto commented Aug 2, 2023

wouldn't we need to pass a pointer to upcall_id in order to not spill to the stack?

Yes, that's what I meant by "[assuming struct upcall_id were passed by ref, unlike the implicit copy I wrote above..]"

I'm generally wary about relying on compiler optimizations for performance correctness, but I think we can shelve this side-hypothetical for now.


Note that libtock-rs today only even allows one outstanding callback

Why is that limitation in place? That's a pretty crippling limitation for a lot of applications — can't have something like a periodic timer and a event-based sensor subscription live at the same time? That's not really an acceptable or realistic limitation for the long term.

If the issue is concurrent execution concerns of callbacks, yield_for as-proposed can be used to ensure that only one callback stack/chain is active at any given time.


I'm not opposed to something of the spirit of yield_for_blocking in addition to yield_for, but I think that is a more complex and nuanced interface for the reasons we've articulated, and that the simpler yield_for is useful on its own merits.

@ppannuto
Copy link
Member Author

ppannuto commented Aug 2, 2023

One thing I had been ruminating on was whether there would be value in something closer to a select/poll style interface, or in this context a yeild_for_any_of([array of upcall ids]) (with return value of index of which array entry fired).

In practice, I think the ergonomics around first allowing the array and then sending the next syscall with the pointer is awkward, especially for the likely common-case of just wanting to yield on one specific upcall. I'd be inclined to steer clear of this more complex interface until it's proven necessary.

@bradjc
Copy link
Contributor

bradjc commented Aug 2, 2023

Note that libtock-rs today only even allows one outstanding callback

Why is that limitation in place? That's a pretty crippling limitation for a lot of applications — can't have something like a periodic timer and a event-based sensor subscription live at the same time? That's not really an acceptable or realistic limitation for the long term.

I'm not sure it is? In my little dabble yesterday it seems like subscribe in libtock-rs is essentially the with operator in python: the subscribe is removed when the context goes away, but you can nest "with"s. Maybe that doesn't actually work in practice, but that's the impression the libtock-rs API gives.

But it comes with the two disadvantages that Brad mentioned, and still requires one more context switch than blocking_command().

It's really hard to discuss blocking command without something like this PR. What exactly is blocking command? Critically, how does a capsule give the blocking command its return data? Also, can someone write its #### Disadvantages section? I kinda made it easy by writing my own for the idea I was supporting.

It seems like the major disadvantage is going to be: when writing a capsule driver, after my interrupt fires, do I call grant.sched_upcall() or kernel.command_return()? And when using a capsule, can I call subscribe or do I have to call blocking command?


Imagine we implement yield-for-blocking and it turns out that we like it, and who knows maybe we rename grant.sched_upcall() to grant.deliver() or something and we end up with a nice way to merge an async kernel with a convenient sequential userspace.

It seems like it might not be too big of a step to implement command-yield-for-blocking, which is a command immediately followed by a yield-for-blocking in a single system call (how exactly to do that tbd).

I say this because that to me seems more plausible than re-writing all of our capsules to have both an async and a sync version (if in fact that is what is required for blocking_command).

@jrvanwhy
Copy link
Contributor

jrvanwhy commented Aug 2, 2023

Note that libtock-rs today only even allows one outstanding callback

Why is that limitation in place? That's a pretty crippling limitation for a lot of applications — can't have something like a periodic timer and a event-based sensor subscription live at the same time? That's not really an acceptable or realistic limitation for the long term.

libtock-rs allows multiple outstanding callbacks (e.g. you can wait for multiple sensors concurrently, with a timeout alarm), but it does not allow for independent tasks. That is, you cannot have a callback running in the background that blinks an LED continuously while your code does something completely unrelated to that.

There is more context on the decision to not support independent tasks at tock/libtock-rs#334. The short version is that supporting that requires making a lot of important design decisions that I did not have the data to make at the time.

I also want to re-evaluate whether futures are acceptable in libtock-rs. Over the past few years, I've received a lot of suggestions on how to do more lightweight Future-based APIs than I achieved when I benchmarked the code size impacts of Future. I haven't had the time to investigate those suggestions, but I want to do so, and if any of them pan out then that would we wonderful. Using futures would allow us to support independent tasks without having to make those painful design tradeoffs.

@ppannuto
Copy link
Member Author

ppannuto commented Aug 2, 2023

That state of libtock-rs is much more sane.

w.r.t. to an eventual more async world, there's some more complex stuff in libtock-c apps, but really nothing that would demand more capability than what is sounds like libtock-rs already supports. I do think one of the good outcomes from TockWorld6 is a push for the core team to get more parity in libtock-c / libtock-rs examples, so if I'm wrong, I suspect we'll see soon :).

_Note:_ In the case that the specified upcall is set to the Null Upcall,
no upcall will execute, but Yield-WaitFor will still return. In this
case, the contents of r0-r3 upon return are UNDEFINED and should not be
relied on in any way.
Copy link
Member

Choose a reason for hiding this comment

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

OK, this is great except why not have r0-r3 be the values that would have been passed a non-null upcall? This would allow many cases to avoid callbacks entirely and just process the results directly, and doesn't cost anything (I don't think).

Copy link
Contributor

@bradjc bradjc Aug 4, 2023

Choose a reason for hiding this comment

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

That is yield-for-blocking. Or a variant at least. I see what you are asking. But, given

The Tock kernel MUST NOT invoke the Null Upcall.

what would those be?

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, so, the reason I didn't do this is because I couldn't decide the right behavior for r3.

TRD104 starts by describing upcalls abstractly as something that have (up to) 4 return arguments passed in r0-r3. We have one type of upcall the kernel actually emits right now, namely the upcall produced in response to subscribe [n.b., nothing in this TRD forbids other upcall signatures in the future*]. For clarity, I will use SubscribableUpcall to refer to an upcall invoked on a pointer passed via Subscribe.

Much to my surprise, as far as I can tell, the signature of SubscribableUpcall is not expressly defined anywhere except in §5.2 of this TRD, where the C prototype for a subscribe_upcall is given as an example.

Indeed, we define the FunctionCall object passed to UKB with this note:

Struct that defines a upcall that can be passed to a process. The upcall takes four arguments that are Driver and upcall specific, so they are represented generically here.

In practice, the type signature for an upcall is established by the kernel in kernel::upcall::schedule() and an implicit assumption in the definition/creation of an Upcall object that upcalls come only from subscribes. But that's just an implementation artifact at the moment, not part of our ABI*.

Now, if YieldFor were to say something like 'sets r0-r3 to the Upcall Arguments if the current Upcall is the NullUpcall', then we would expect all Upcall Arguments to be set, right? Now, for the case of a SubscribableUpcall r0-r2 are clear, but what do we do with r3, which would be the userdata pointer? Does YieldFor need to do different things based on which type of Upcall it's passing?

We could further amend TRD104 to formally specify the function signature for all upcalls, and then define YieldFor to skip r3. Or define SubscribableUpcall and specify YieldFor behavior only for that upcall type.

I was shooting for minimum delta with first RFC, and I don't think we can do this "skip the callback" without also making more changes around Upcall definition. I lean to formalizing SubscribableUpcall as a no-overhead option for today (since there is only one Upcall type still) while leaving the door open to other Upcall signatures in the future.

I do think the "skip the callback" would be nice. I can update this RFC to capture a version of that.


*Subscribe does start like this:

The Subscribe system call class is how a userspace process registers upcalls with the kernel.

Which if we really read into the "a" there maybe suggests the 1:1 mapping, but I wouldn't blink an eye at a future section that said something like "The GetASignal system call is another way userspace processes register upcalls with the kernel". If there really is OneTrueUpcallSignature, this TRD needs to say that much more directly.

Copy link
Contributor

Choose a reason for hiding this comment

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

*Subscribe does start like this:

The Subscribe system call class is how a userspace process registers upcalls with the kernel.

Which if we really read into the "a" there maybe suggests the 1:1 mapping, but I wouldn't blink an eye at a future section that said something like "The GetASignal system call is another way userspace processes register upcalls with the kernel". If there really is OneTrueUpcallSignature, this TRD needs to say that much more directly.

But wouldn't we need to add that everywhere? A command syscall is THE ONLY way to issue a command. Yield is THE ONLY way to yield, etc. It seems clear to me that subscribe is the only way to subscribe, since that is all it does.

But yes we need to document the upcall signature.

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree it's clear that "subscribe is the only way to subscribe".

What I do not think is clear is that "subscribe is the only way to get upcalls". Upcalls are defined completely independently of subscribe in the previous section. As written, subscribe is just one thing (and currently happens to be the only thing) than can generate an upcall.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need to say anything about subscribe being the only way to set upcalls, any more than we need to say command is the only way to instruct the kernel to start an operation. Commands are documented elsewhere. But, we do need to define what an upcall is, in my opinion.

@alevy
Copy link
Member

alevy commented Aug 4, 2023

I'm strongly supportive of something very close to this.

High bit

Yield-WaitFor solves several related problems concisely and elegantly:

  1. It allows for true blocking semantics, most notably for debug statements in callbacks, where "simulated" blocking using the user-space yield_for relies heavily on user-written callbacks not, themselves calling yield_for recursively, e.g.. This is a big semantic improvement to userspace.

  2. It can dramatically reduce the code size required of user-space applications that using a synchronous I/O API---they could avoid callbacks all-together, avoid corresponding subscribe calls, and reduces the number of system calls invoked per operation for some common operations. This seems particularly useful in Rust applications where having callbacks always subscribed in the background (without unsubscribing them) may be unsound.

  3. It doesn't require any additional functionality in capsules to support this. Everything is entirely differentiated in the process scheduler. This is important because it means that any code size implications in the kernel are fixed, and it means that userspace doesn't need to wait for a particular capsule to adapt to the new API in order to use this new yield variant.

Some more details separated so they can be quoted separately

More on 1: it's worth looking through our various libtock-c examples and drivers and seeing where we would actually replace calls to the current library yield_for with a new Yield-WaitFor. In practice, these have slightly different semantics, so it's not obvious it would be everywhere, but it might be everywhere or almost everywhere.

More on 2: with some specialization in userspace as well as some sort of combined system-call system call ("please do a command followed by a yield-waitfor") I think we can get nearly optimal code size in applications (compared to a functionality-specific specialized blocking call). So we should view this as a step towards even more code size optimization.

(There are no!) Disadvantages

@bradjc points out some disadvantages. These are very well worth considering. I think an evolution of this proposal avoids these pitfalls.

  1. Semantic confusion: schedule_upcall() in a capsule may not, actually, schedule an upcall any more. Instead the "upcall" happens as the return to a yield.

Yes. As suggested later in the thread, either renaming or aliasing schedule_upcall to deliver may help avoid this confusion. I think this is just a naming issue, which we should resolve, but doesn't break this proposal.

  1. If userspace does call subscribe, but then only uses yield-for-blocking, that upcall would never get called, which is odd. But with a mix of yield-for-blocking and yield, the upcall would get called sometimes. This inconsistency complicates the interface.

Agree completely. Yield-WaitFor should use the existence of a subscribed callback to determine whether to execute a callback or return directly to the system call sight with the values passed to schedule_upcall. If a process has a callback subscribed, that callback should always execute. If it doesn't, Yield-WaitFor should always return the values that would have been passed to a callback.

There is a remaining question of what to do with r0-r3 if a callback was executed. I think, in this case, it might be reasonable to allow the callback (currently a void) to determine what those values should be.

@alevy alevy changed the title [RFC] YieldFor syscall [RFC] Yield-WaitFor syscall Aug 4, 2023
@alevy
Copy link
Member

alevy commented Aug 4, 2023

Naming versions of the Yield variant

We've now described several variations of the proposed yield variant and I believe I've added to the confusion by conflating names for them above. Let's refer to them as follows:

  • Yield-WaitFor is the original proposal as described in this revision of the TRD text
  • Yield-WaitFor-NoCallback (proposed by @bradjc) never executes a callback, regardless of whether one is registered, and instead returns the values that would be passed to the callback as return values to the system call site.
  • Yield-WaitFor-CallbackIfPresent (proposed implicitly by me) always executes a callback if one is present, otherwise it returns directly to the callsight returning the values that would have been passed to the callback in registers r0-r3.
  • Yield-WaitFor-OptionalSubscribe (proposed by @kupiakos) yield passes an upcall handler if it wants to use an upcall (which replaces any existing subscribed upcall handler), otherwise upcall values are passed as return values to the system call site.

@bradjc
Copy link
Contributor

bradjc commented Aug 4, 2023

What is the usecase for calling subscribe and using Yield-WaitFor-CallbackIfPresent? Maybe writing async code with ordering semantics where the code can know which order the callbacks will happen?

@alevy
Copy link
Member

alevy commented Aug 4, 2023

What is the usecase for calling subscribe and using Yield-WaitFor-CallbackIfPresent? Maybe writing async code with ordering semantics where the code can know which order the callbacks will happen?

One simple example may be logging statistics on how often something happens.

If the callback is able to return different values to the syscall site in r0-r3, it might be useful for filtering or customizing the result...

Just general modularity.

Indeed, I suspect this will rarely be used.

@ppannuto
Copy link
Member Author

ppannuto commented Aug 5, 2023

I've updated this to match the Yield-WaitFor-CallbackIfPresent proposed by @alevy.

There are some design choices not fully elucidated in the RFC yet, as I'd like us to decide what we want to do about #3577 (comment) before fleshing that text out completely.

PoC code is updated (again, compile-tested only) to realize the proposed interface.

@bradjc
Copy link
Contributor

bradjc commented Aug 7, 2023

If the callback is able to return different values to the syscall site in r0-r3, it might be useful for filtering or customizing the result...

Is Yield-WaitFor-CallbackIfPresent either or, or both? I think it is either: either the upcall is called or yield returns r0-r3, but not both. So if you use Yield-WaitFor-CallbackIfPresent with a subscribe then it can't also use r0-r3. Or are you saying those would be some other values instead of the upcall return values? But, reading the TRD again, I don't think we can both call an upcall and return values.

| yield-param-2 | r2 |
| yield-param-3 | r3 |

The Yield system call class has no return value. This is because
Copy link
Contributor

Choose a reason for hiding this comment

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

This would no longer be true.

@bradjc
Copy link
Contributor

bradjc commented Aug 7, 2023

With Yield-WaitFor-CallbackIfPresent, is it true that userspace can't determine at runtime if r0-r3 are valid, or if an upcall executed?

So libtock tries to write:

int my_command_sync() {
  driver_command();
  asm("yield wait-for-callbackifpresent");
  // did an upcall execute?
  // or is r0-r3 valid?
  return ??
}

But the then the user at some point called subscribe(my_driver) and then forgot about it. So when the user calls my_command_sync() it seems to work ok, but the values are wrong.

Is this possible or am I missing something?

@ppannuto
Copy link
Member Author

ppannuto commented Aug 7, 2023

With Yield-WaitFor-CallbackIfPresent, is it true that userspace can't determine at runtime if r0-r3 are valid, or if an upcall executed?

As currently written, this is correct, yes. It assumes that userspace tracks whether it has subscribed a callback or not correctly, and if userspace cares that a callback ran, the callback can set a flag.

We could use yield-param-3 to do the same pointer-indicator thing as Yield-NoWait if desired.

@bradjc
Copy link
Contributor

bradjc commented Aug 7, 2023

With Yield-WaitFor-CallbackIfPresent, is it true that userspace can't determine at runtime if r0-r3 are valid, or if an upcall executed?

As currently written, this is correct, yes. It assumes that userspace tracks whether it has subscribed a callback or not correctly, and if userspace cares that a callback ran, the callback can set a flag.

We could use yield-param-3 to do the same pointer-indicator thing as Yield-NoWait if desired.

I propose we pass all yield return arguments via a pointer specified by yield-param-3 unconditionally.

Otherwise, I don't know how we would use Yield-WaitFor-CallbackIfPresent in userspace libraries other than calling subscribe(driver, upcall, 0) before Yield-WaitFor-CallbackIfPresent every time.

OR, I prefer Yield-WaitFor-NoCallback to avoid this potential error entirely (and it would not be a fun bug to debug I suspect).

bradjc
bradjc previously requested changes Aug 10, 2023
Copy link
Contributor

@bradjc bradjc left a comment

Choose a reason for hiding this comment

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

It would be great if I was missing something, but it seems with Yield-WaitFor-CallbackIfPresent a userspace implementation of yield_for is unable to tell if the registers after the yield_for syscall returns are valid or not, without some other tracking state or program knowledge. Marking as blocked to resolve this, because that seems like a problematic interface we do not want.

@alevy alevy added the P-Significant This is a substancial change that requires review from all core developers. label Jun 16, 2024
alevy
alevy previously approved these changes Jun 16, 2024
@alevy
Copy link
Member

alevy commented Jun 16, 2024

@tock/core-wg This is definitely P-Significant, and I marked it so, so we need another approval. Having said that, I think this is pretty good to go.

Copy link
Contributor

@alexandruradovici alexandruradovici left a comment

Choose a reason for hiding this comment

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

I suggested adding a few comments, as it was not clear for me when reading the code that the registered callback will not be called. I had to read the TRD for this. I think this will make code understanding easier.

@alevy
Copy link
Member

alevy commented Jun 16, 2024

@alexandruradovici Comments adapted. I think all good.

In general, I don't love the structure of the Task enum, which is now kind pulling double-duty, and thus requires a bit more documentation (as you've added) to make clear how it's actually used. Having said that, I think it's worth deferring refactoring the code until IPC is potentially reworked, which might unify Task::IPC and Task::FunctionCall, and overall result in cleaner code.

@alevy alevy added this pull request to the merge queue Jun 16, 2024
Merged via the queue into master with commit db6b982 Jun 17, 2024
@alevy alevy deleted the yeild-for branch June 17, 2024 00:03
@bradjc
Copy link
Contributor

bradjc commented Jun 17, 2024

How did we merge a significant PR in 3 days with only 2 approvals?

@bradjc
Copy link
Contributor

bradjc commented Jun 17, 2024

Did anyone else test this? I ran exactly one test on one board with one process. And nothing on riscv.

@alevy
Copy link
Member

alevy commented Jun 17, 2024

We need 2 approvals for a p-significants

@bradjc
Copy link
Contributor

bradjc commented Jun 17, 2024

We need 2 approvals for a p-significants

Can we write this down somewhere? That is not my understanding nor what our documentation says

Significant pull requests require review by the entire core team. Each
core team member is expected to respond within one week.

@bradjc
Copy link
Contributor

bradjc commented Jun 17, 2024

I guess this PR is a bit of a grey area because it was a draft for a year, but it wasn't clear how close that draft was to mergable, and core members should have a week to look at significant PRs. After a week 2 approvals makes sense.

@alevy
Copy link
Member

alevy commented Jun 17, 2024

Ok, sorry.

My sense was that it had sat since your rebase and testing, I confirmed with similar testing, and there has been for a while either consensus or apathy about the design (I think I'm the most dissenting voice about the design specifically). And we'll do more testing for release and now that it's merged.

We can/should clarify that language. Maybe your interpretation is right and I was wrong, maybe my interpretation was right, but either way it's not clear

lschuermann added a commit to lschuermann/tock that referenced this pull request Jun 17, 2024
Merging the YieldFor PR has changed the TRD, but did not update its
draft version: "Merge pull request tock#3577 from tock/yeild-for"
(db6b982)
Copy link
Member

Choose a reason for hiding this comment

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

We should also update the draft version of this TRD in a follow-up PR.

Copy link
Member

Choose a reason for hiding this comment

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

Created a PR: #4032

@lschuermann
Copy link
Member

We can/should clarify that language. Maybe your interpretation is right and I was wrong, maybe my interpretation was right, but either way it's not clear

I don't know whether requiring approvals from all core team members is feasible in practice, but we should definitely allow for more time from the point where reviews from the WG are requested, to when someone is able to hit merge.

lschuermann added a commit to lschuermann/tock that referenced this pull request Jun 17, 2024
…hing`

`Queue`'s `dequeue_specific` was renamed to `remove_first` in
ab293c5 ("Rename dequeue_specific to remove_first").

While I don't disagree with the fact that `dequeue_specific` is an
oxymoron and a bad name, arguably `remove_first` carries its own
baggage. We're talking about collections where "first" has meaning,
often used interchangably with "head". Looking over the code in tock#3577
I was quite surprised with the complexity of this method's
implementation in `RingBuffer`, for what I thought would just be a
`dequeue` operation---removing the _first_ / _head_ element.

This is an effort to clear up that confusion. This is not a hill worth
dying on for me, so if anyone has strong feelings otherwise, feel free
to close this.
lschuermann added a commit to lschuermann/tock that referenced this pull request Jun 17, 2024
Merging the YieldFor PR has changed the TRD, but did not update its
draft version or date modified attribute: "Merge pull request tock#3577
from tock/yeild-for" (db6b982)
@alistair23
Copy link
Contributor

How did we merge a significant PR in 3 days with only 2 approvals?

It's also still an RFC

alevy pushed a commit to alevy/tock that referenced this pull request Jul 1, 2024
…hing`

`Queue`'s `dequeue_specific` was renamed to `remove_first` in
ab293c5 ("Rename dequeue_specific to remove_first").

While I don't disagree with the fact that `dequeue_specific` is an
oxymoron and a bad name, arguably `remove_first` carries its own
baggage. We're talking about collections where "first" has meaning,
often used interchangably with "head". Looking over the code in tock#3577
I was quite surprised with the complexity of this method's
implementation in `RingBuffer`, for what I thought would just be a
`dequeue` operation---removing the _first_ / _head_ element.

This is an effort to clear up that confusion. This is not a hill worth
dying on for me, so if anyone has strong feelings otherwise, feel free
to close this.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kernel P-Significant This is a substancial change that requires review from all core developers.

Projects

None yet

Development

Successfully merging this pull request may close these issues.