"Nonblocking" specifier

160 views
Skip to first unread message

Derek Hofmann

unread,
Jul 14, 2016, 6:38:09 PM7/14/16
to ISO C++ Standard - Future Proposals
Similar to the "const" specifier that prevents calling a non-const member function, I would like a specifier that indicates that a function is a nonblocking function and can only call other nonblocking functions. This would prevent a whole class of bugs in multithreaded applications.

Another person had a similar idea but thought a more general solution of allowing custom type modifiers might be better: http://allievi.sssup.it/techblog/archives/705

Has anything like this been proposed?

    Derek

Thiago Macieira

unread,
Jul 14, 2016, 7:49:44 PM7/14/16
to std-pr...@isocpp.org
For the first time since I've been a member of this mailing list, I think we
can safely say that this IS a good use of attributes.

This is like the [[pure]] attribute that was proposed but not yet accepted.
But unlike the [[pure]] attribute, this is just a compiler-aided check.

Maybe you can widen your proposal to something like attribute
[[ensures(xxx)]], which can only call other functions that have the same xxx
in their [[ensures(...)]] declaration. This would allow for different tags,
not just "nonblocking".

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Thiago Macieira

unread,
Jul 14, 2016, 8:05:27 PM7/14/16
to std-pr...@isocpp.org
On quinta-feira, 14 de julho de 2016 16:49:38 PDT Thiago Macieira wrote:
> > Another person had a similar idea but thought a more general solution of
> > allowing custom type modifiers might be better:
> > http://allievi.sssup.it/techblog/archives/705
> >
> > Has anything like this been proposed?

> Maybe you can widen your proposal to something like attribute
> [[ensures(xxx)]], which can only call other functions that have the same
> xxx in their [[ensures(...)]] declaration. This would allow for different
> tags, not just "nonblocking".

Uh... which is what you had said.

This can also go further and be used for types, allowing the compiler to
complain when you mix two objects of the same type, but not of the same "tag".
You can see an example of that in the Linux kernel, where user pointers are
marked __user and there's some tool somewhere that checks whether you made an
assignment you shouldn't have.

This would also require a library function to "launder" the type and erase the
attributes.

Derek Hofmann

unread,
Jul 15, 2016, 1:22:21 PM7/15/16
to ISO C++ Standard - Future Proposals
Thiago,

Thanks for the words of support.

Thinking about this a little more, in order to mix well with old code, it needs a way to override the absence of the specifier or attribute in the way that "const_cast" and "mutable" override "const". I was thinking that blocks (scopes) of code within a function could be tagged as conformant with the specifier or attribute:

    void BlockingFunc() // All functions are considered to be blocking by default
   
{
   
}

   
[[ensures(nonblocking)]] void NonblockingFunc()
   
{
   
}

   
[[ensures(nonblocking)]] void MyFunc()
   
{
       
BlockingFunc(); // error: "nonblocking" attribute not specified

        conforms
(nonblocking)
       
{
           
BlockingFunc(); // no error
       
}

       
NonblockingFunc(); // no error
   
}


But a simple conversion cast should also work:

    [[ensures(nonblocking)]] void MyFunc()
   
{
        conform_cast
<nonblocking>( BlockingFunc() ); // no error
   
}

Which of the two works better, or is there a better way to mix with old code than either example above?

    Derek

inkwizyt...@gmail.com

unread,
Jul 15, 2016, 3:51:19 PM7/15/16
to ISO C++ Standard - Future Proposals


On Friday, July 15, 2016 at 7:22:21 PM UTC+2, Derek Hofmann wrote:
Thiago,

Thanks for the words of support.

Thinking about this a little more, in order to mix well with old code, it needs a way to override the absence of the specifier or attribute in the way that "const_cast" and "mutable" override "const". I was thinking that blocks (scopes) of code within a function could be tagged as conformant with the specifier or attribute:

    void BlockingFunc() // All functions are considered to be blocking by default
   
{
   
}

   
[[ensures(nonblocking)]] void NonblockingFunc()
   
{
   
}

   
[[ensures(nonblocking)]] void MyFunc()
   
{
       
BlockingFunc(); // error: "nonblocking" attribute not specified

        conforms
(nonblocking)
       
{
           
BlockingFunc(); // no error
       
}

       
NonblockingFunc(); // no error
   
}


I think you use incorrect syntax. Because "nonblocking" is attribute then disabling it should be too.

Proper syntax should be something like that:
[[ensures(nonblocking)]] void MyFunc()
{
 
int x = [[ignore(nonblocking)]] funcA();
 
[[ignore(nonblocking)]]
 
{
    funcB
();
    funcV
();
 
}
}


 

Thiago Macieira

unread,
Jul 15, 2016, 5:57:27 PM7/15/16
to std-pr...@isocpp.org
On sexta-feira, 15 de julho de 2016 10:22:21 PDT Derek Hofmann wrote:
> Thinking about this a little more, in order to mix well with old code, it
> needs a way to override the absence of the specifier or attribute in the
> way that "const_cast" and "mutable" override "const". I was thinking that
> blocks (scopes) of code within a function could be tagged as conformant
> with the specifier or attribute:

That is true, but it can be done by the library function that "launders" the
attribute. You'd have to use function pointers, but it would work.

Derek Hofmann

unread,
Jul 15, 2016, 6:03:53 PM7/15/16
to ISO C++ Standard - Future Proposals, inkwizyt...@gmail.com
That's a good catch, inkwizyt.

Besides "nonblocking", another tag people might want to use is "reentrant". And there's already a topic about it: https://groups.google.com/a/isocpp.org/d/msg/std-proposals/yBLLDuf_1ck/cBIYsZRepvEJ

Thiago Macieira

unread,
Jul 15, 2016, 7:45:30 PM7/15/16
to std-pr...@isocpp.org
On sexta-feira, 15 de julho de 2016 15:03:53 PDT Derek Hofmann wrote:
> That's a good catch, inkwizyt.
>
> Besides "nonblocking", another tag people might want to use is "reentrant".
> And there's already a topic about it:

Also "safe" or "untrusted".

Basically, the proposal should be for generic tags, such that the compiler
should produce a diagnostic when calling an untagged function from a tagged
one (or vice-versa), or assigning a tagged variable to an untagged one (or
vice-versa)

Nicol Bolas

unread,
Jul 16, 2016, 1:02:07 PM7/16/16
to ISO C++ Standard - Future Proposals
On Friday, July 15, 2016 at 7:45:30 PM UTC-4, Thiago Macieira wrote:
On sexta-feira, 15 de julho de 2016 15:03:53 PDT Derek Hofmann wrote:
> That's a good catch, inkwizyt.
>
> Besides "nonblocking", another tag people might want to use is "reentrant".
> And there's already a topic about it:

Also "safe" or "untrusted".

Basically, the proposal should be for generic tags, such that the compiler
should produce a diagnostic when calling an untagged function from a tagged
one (or vice-versa), or assigning a tagged variable to an untagged one (or
vice-versa)

I don't know. I think, conceptually speaking, there are 3 levels of functions per-tag:

* Has the tag.
* Does not have the tag.
* Deliberately does not have the tag.

The distinction in the last two is, I think important. Consider "nonblocking". A function can be tagged with nonblocking, and thus should be reasonable to call from a function tagged nonblocking. But if a function is not tagged `nonblocking`... what does that mean? Is the function guaranteed to be a blocking call? Or is it just a function which wasn't given a tag? Is it reasonable to consider `std::sqrt` to be a "blocking call", just because it wasn't tagged appropriately?

So really, what we have is "has the tag", "has not the tag", and "property unknown". And the implementer of a function should be able to decide whether it is reasonable to call "property unknown" functions from their tagged function.

I think it would be something like this:

[[require(tagname)]] void func1()
{
 
//Can only call functions that have `tagname`
 
//func1 is considered to have `tagname`
}

[[allow(tagname)]] void func2()
{
 
//Cannot call functions that have `!tagname`.
 
//func2 is considered to have `tagname`.
}

[[require(!tagname)]] void func3
{
 
//Can only call functions that have `!tagname`.
 
//func3 is considered to have `!tagname`.
}

[[
allow(!tagname)]] void func4
{
 
//Cannot call functions that have `tagname`
 
//func4 is considered to have `!tagname`.
}

So for each tagname, a function can either have that `tagname`, have `!tagname`, or be untagged with regard to that tag. And a function can either allow calling `tagname` only, calling `tagname` and untagged, calling `!tagname` only, or calling `!tagname` and untagged.

Thiago Macieira

unread,
Jul 16, 2016, 2:05:19 PM7/16/16
to std-pr...@isocpp.org
On sábado, 16 de julho de 2016 10:02:07 PDT Nicol Bolas wrote:
> The distinction in the last two is, I think important. Consider
> "nonblocking". A function can be tagged with nonblocking, and thus should
> be reasonable to call from a function tagged nonblocking. But if a function
> is not tagged `nonblocking`... what does that mean? Is the function
> *guaranteed* to be a blocking call? Or is it just a function which wasn't
> given a tag? Is it reasonable to consider `std::sqrt` to be a "blocking
> call", just because it wasn't tagged appropriately?

And then there are the functions that may block, given some arguments.

For example, all the waitForXxx functions in Qt with a timeout: if the timeout
is zero, they don't block.

Derek Hofmann

unread,
Jul 16, 2016, 5:03:18 PM7/16/16
to std-pr...@isocpp.org

For me, I'm mainly concerned about whether a function is guaranteed safe (nonblocking) or not guaranteed safe. Being able to distinguish between two substates of "not guaranteed safe" (unknown vs. guaranteed unsafe) adds a little value but a lot of complexity to the specification.

What's a situation where 3 levels per tag is needed?


--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/q8bMB4ZTl4E/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/c38a4b14-964b-4603-baff-37e9f7abbebd%40isocpp.org.

Nicol Bolas

unread,
Jul 16, 2016, 8:13:17 PM7/16/16
to ISO C++ Standard - Future Proposals
On Saturday, July 16, 2016 at 5:03:18 PM UTC-4, Derek Hofmann wrote:

For me, I'm mainly concerned about whether a function is guaranteed safe (nonblocking) or not guaranteed safe. Being able to distinguish between two substates of "not guaranteed safe" (unknown vs. guaranteed unsafe) adds a little value but a lot of complexity to the specification.

What's a situation where 3 levels per tag is needed?


Pretty much any time you try to break into the world of code that you didn't write and don't have control over.

For your blocking example, there's no reason to consider, for instance, `sqrt` to be a blocking call. But it doesn't have an appropriate tag in it. There are many function in `vector` which don't allocate memory, yet they aren't tagged nonblocking either. And so forth.

Really, for something like "blocking", I would much rather annotate the functions that are known to be blocking and then declare functions that aren't allowed to call blocking functions.

Also, what about templates? A function like `sort` could be blocking or nonblocking, depending on what the function it is given does. How would that work?

Magnus Fromreide

unread,
Jul 17, 2016, 1:36:42 AM7/17/16
to std-pr...@isocpp.org
On Sat, Jul 16, 2016 at 10:02:07AM -0700, Nicol Bolas wrote:
> On Friday, July 15, 2016 at 7:45:30 PM UTC-4, Thiago Macieira wrote:
> >
> > On sexta-feira, 15 de julho de 2016 15:03:53 PDT Derek Hofmann wrote:
> > > That's a good catch, inkwizyt.
> > >
> > > Besides "nonblocking", another tag people might want to use is
> > "reentrant".
> > > And there's already a topic about it:
> >
> > Also "safe" or "untrusted".
> >
> > Basically, the proposal should be for generic tags, such that the compiler
> > should produce a diagnostic when calling an untagged function from a
> > tagged
> > one (or vice-versa), or assigning a tagged variable to an untagged one (or
> > vice-versa)
> >
>
> I don't know. I think, conceptually speaking, there are 3 levels of
> functions per-tag:
>
> * Has the tag.
> * Does not have the tag.
> * Deliberately does not have the tag.
>
> The distinction in the last two is, I think important. Consider
> "nonblocking". A function can be tagged with nonblocking, and thus should
> be reasonable to call from a function tagged nonblocking. But if a function
> is not tagged `nonblocking`... what does that mean? Is the function
> *guaranteed* to be a blocking call? Or is it just a function which wasn't
> given a tag? Is it reasonable to consider `std::sqrt` to be a "blocking
> call", just because it wasn't tagged appropriately?
>
> So really, what we have is "has the tag", "has *not* the tag", and
> "property unknown". And the implementer of a function should be able to
> decide whether it is reasonable to call "property unknown" functions from
> their tagged function.
>
> I think it would be something like this:
>
> [[require(tagname)]] void func1()
> {
> //Can only call functions that have `tagname`
> //func1 is considered to have `tagname`
> }
>
> [[allow(tagname)]] void func2()
> {
> //Cannot call functions that have `!tagname`.
> //func2 is considered to have `tagname`.
> }
>
> [[require(!tagname)]] void func3
> {
> //Can only call functions that have `!tagname`.
> //func3 is considered to have `!tagname`.
> }
>
> [[allow(!tagname)]] void func4
> {
> //Cannot call functions that have `tagname`
> //func4 is considered to have `!tagname`.
> }
>
> So for each tagname, a function can either have that `tagname`, have
> `!tagname`, or be untagged with regard to that tag. And a function can
> either allow calling `tagname` only, calling `tagname` and untagged,
> calling `!tagname` only, or calling `!tagname` and untagged.

When I read this thread I see a lot of similarites with exception specifiers
and I am wondering if there is a need for tagged blocks for this as well,
something along the lines of

[[require(tagname)]] void func1()
{
...
[[promise(tagname)]] { // The programmer promises that the calls in
// this block fulfils the requirements of
// tagname even if they are !tagname
func3();
}
...
}

where this is inspired by the try-blocks of exception specifications.

/MF

Arthur O'Dwyer

unread,
Jul 17, 2016, 7:04:04 PM7/17/16
to ISO C++ Standard - Future Proposals
On Thursday, July 14, 2016 at 5:05:27 PM UTC-7, Thiago Macieira wrote:
On quinta-feira, 14 de julho de 2016 16:49:38 PDT Thiago Macieira wrote:
> > Another person had a similar idea but thought a more general solution of
> > allowing custom type modifiers might be better:
> > http://allievi.sssup.it/techblog/archives/705
> >
> > Has anything like this been proposed?

> Maybe you can widen your proposal to something like attribute
> [[ensures(xxx)]], which can only call other functions that have the same
> xxx  in their [[ensures(...)]] declaration. This would allow for different
> tags, not just "nonblocking".

Uh... which is what you had said.

Not only has this idea been proposed, it has an implementation (for C) in the research project "CQUAL", circa 2002.
The idea is to permit arbitrary additions to the set of cv-qualifiers, with the usual rules about which cv-qualifiers are allowed to be implicitly added or dropped to which kinds of expressions.  For example:
- A "string [[untrusted]] &" cannot be converted to a "string&" except through a laundering function (such as const_cast).
- A member function qualified with "[[nonblocking]]" implicitly adds the [[nonblocking]] qualifier to its "this" object, so it can't call non-[[nonblocking]] methods on that object (without laundering, that is).
- An arbitrary "char *" cannot be passed to a function that expects a "char [[kernelspace]] *" (without laundering, that is).

CQUAL's type-qualifier-based approach probably isn't 100% what Derek wants, because it applies only to objects, not to control flow (i.e. my bullet point above about [[nonblocking]] explicitly doesn't say that you're only allowed to call [[nonblocking]] functions; it says you're only allowed to call [[nonblocking]] methods on the this object).  However, I think you could work around this in practice, if the feature were valuable enough to you, by passing a cv-qualified "context" object to all your non-OOP functions:

    void frotz(context *ctx, int value);  // blocking by default

    void frobnicate([[nonblocking]] context *ctx, Some&& arguments)
    {
        frotz(ctx, 42);  // overload resolution fails because ([[nonblocking]] context *) isn't implicitly convertible to (context *)
    }

Mind you, if you cared that much, you'd probably be doing the above already, using custom tag types.  The GSL's "not_null<T*>" is an example of that approach: (not_null<T *>) is implicitly convertible to (T *) but not vice versa.

Given that you can already do this with GSL-style tag types, and I don't think the CQUAL-style approach would actually result in much less typing (just more complication to the type system), my thinking right now is that there's not a compelling reason to pursue the CQUAL-style approach at the moment.

Derek, have you tried the GSL/not_null-style approach in a real codebase?  If so, how did it do?

–Arthur

Ren Industries

unread,
Jul 17, 2016, 7:51:05 PM7/17/16
to std-pr...@isocpp.org
This seems very similar, in principle, to Boost.Units.
Could it not be implemented similarly, in current existing C++14?

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Bjorn Reese

unread,
Jul 23, 2016, 10:59:30 AM7/23/16
to std-pr...@isocpp.org
On 07/16/2016 01:45 AM, Thiago Macieira wrote:
> On sexta-feira, 15 de julho de 2016 15:03:53 PDT Derek Hofmann wrote:

>> Besides "nonblocking", another tag people might want to use is "reentrant".
>> And there's already a topic about it:
>
> Also "safe" or "untrusted".

Or signal-safe as added by P0270R0.
Reply all
Reply to author
Forward
0 new messages