Jump to page: 1 2 3
Thread overview
Drawbacks of exceptions being globally allocated
Aug 14, 2021
Tejas
Aug 14, 2021
Tejas
Aug 14, 2021
Alexandru Ermicioi
Aug 14, 2021
Tejas
Aug 14, 2021
Paul Backus
Aug 15, 2021
Tejas
Aug 15, 2021
Ali Çehreli
Aug 15, 2021
Tejas
Aug 15, 2021
Tejas
Aug 15, 2021
Mathias LANG
Aug 15, 2021
Tejas
Aug 15, 2021
Alexandru Ermicioi
Aug 15, 2021
Ali Çehreli
Aug 15, 2021
Alexandru Ermicioi
Aug 15, 2021
Tejas
Aug 15, 2021
Paul Backus
Aug 16, 2021
Tejas
Aug 16, 2021
Paul Backus
Aug 16, 2021
Tejas
Aug 16, 2021
Mathias LANG
Aug 16, 2021
Tejas
Aug 16, 2021
Alexandru Ermicioi
Aug 16, 2021
Tejas
August 14, 2021

What is the drawback of the following "simple" @nogc exception creation technique?

import std;
void main()@nogc
{
    try{
        __gshared a = new Exception("help");
        scope b = a;
        throw b;
    }
    catch(Exception e){
        printf("caught");
    }
}
August 14, 2021

On Saturday, 14 August 2021 at 11:41:36 UTC, Tejas wrote:

>

What is the drawback of the following "simple" @nogc exception creation technique?

import std;
void main()@nogc
{
    try{
        __gshared a = new Exception("help");
        scope b = a;
        throw b;
    }
    catch(Exception e){
        printf("caught");
    }
}

I mean, there has to be a gotcha, some weird multi-threading case where this catastrophically breaks and is therefore not recommended, right? Otherwise, why can't we just use this instead of fretting with DIP 1008? I even tried accessing the actual object in the code below, it didn't crash the program :D

import std;
void main()@nogc
{
    try{
        __gshared a = new Exception("help");
        scope b = a;
        throw b;
    }
    catch(Exception e){
        printf("caught\n");
        printf(cast(char*)e.msg);//new code
    }
}
August 14, 2021

On Saturday, 14 August 2021 at 13:24:22 UTC, Tejas wrote:

>

...

I don't think there are any gotchas here. The problem with this technique, is when your exceptions aren't just simple labels but also carry some additional data, say for example specific error type, and subject that, caused this. In such cases you can't have a gloablly shared instance. Let's say it's doable but has lot's of drawbacks.

Regards,
Alexandru.

August 14, 2021

On Saturday, 14 August 2021 at 15:28:36 UTC, Alexandru Ermicioi wrote:

>

On Saturday, 14 August 2021 at 13:24:22 UTC, Tejas wrote:

>

...

I don't think there are any gotchas here. The problem with this technique, is when your exceptions aren't just simple labels but also carry some additional data, say for example specific error type, and subject that, caused this. In such cases you can't have a gloablly shared instance. Let's say it's doable but has lot's of drawbacks.

Regards,
Alexandru.

Thank you.

If you're willing to help further, would you please tell me why there is a GC allocation in the code below that uses emplace? Will such code truly work if GC is never linked in the program?


import core.lifetime:emplace;
import core.stdc.stdlib:malloc;
int foo(int)@nogc
{
    auto mem = cast(Exception)malloc(__traits(classInstanceSize, Exception));
    auto memo = emplace!(Exception,string)(mem, "HOHOH");
    //scope b = a;
    throw memo;
}

void test() @nogc
{
    try
    {
        foo(1);
    }
    catch(Exception e)
    {
    }
}

void main()
{
    import std.stdio;
    import core.memory;
    auto stats1 = GC.stats();
    test();
    auto stats2 = GC.stats();
    writeln(stats1);
    writeln(stats2);
}

Output:
Stats(0, 0, 0)
Stats(1376, 1047200, 1360)

August 14, 2021

On Saturday, 14 August 2021 at 15:58:17 UTC, Tejas wrote:

>

If you're willing to help further, would you please tell me why there is a GC allocation in the code below that uses emplace? Will such code truly work if GC is never linked in the program?

https://run.dlang.io/is/XEc2WJ

onlineapp.d(26): Error: `@nogc` function `D main` cannot call non-@nogc function `core.memory.GC.stats`

Looks like GC.stats uses the GC.

August 14, 2021
On 8/14/21 4:41 AM, Tejas wrote:

> What is the drawback of the following "simple" ```@nogc``` exception
> creation technique?
>
> ```d
> import std;
> void main()@nogc
> {
>      try{
>          __gshared a = new Exception("help");
>          scope b = a;
>          throw b;
>      }
>      catch(Exception e){
>          printf("caught");
>      }
> }
> ```

So, there would be many exception objects one for each place that an exception can be thrown. Functions like enforce() would have to take a reference to the exception object that is associated with that local scope.

I don't have a clear idea on whether it would work or whether it would be cumbersome to use or not. I wrote the following by misunderstanding you. I thought you you were proposing just one exception object for the whole program. I am still posting it because it is something I realized relatively recently.

Even though this feature is probably never used, in D, multiple exception objects are chained. For example, you can throw e.g. in a destructor when there is an active exception in flight and that second object gets attached to the first one in linked list fashion.

This may be useful in some cases but in general, these colatteral exceptions don't carry much information and I don't think anybody looks at them. Usually, the first one is the one that explains the error case.

All such collateral exceptions are accessible through the Throwable.next function.

However, even if D did not have such a feature and it had only a single exception that could be thrown (like in C++), the reality is, there can be infinite number of exceptions objects alive. This fact is true for C++ as well and this fact is one of the main reasons why exceptions are not allowed in safety-critical systems: When you can't limit the number of exception objects, you can't guarantee that the system will not run out of memory.

Here is how even in C++ there can be infine exception objects. (Note: Yes, there is only one in flight but there is no limit on the number of caught exception objects that are alive.)

try {
  foo();

} catch (Exception exc) {
  // Note: We caught the exception; so it's not in flight anymore

  bar();

  // For the following to work, exc must be alive even after bar()
  writeln(exc.msg);
}

Now imagine bar() had a try-catch of its own where it caught another exception. During the execution of bar's catch clause, there are two exception objects alive.

So, the problem with your proposal is, there is no room for the exception that was throw during bar's execution.

Ali

August 15, 2021

On Saturday, 14 August 2021 at 23:14:51 UTC, Paul Backus wrote:

>

On Saturday, 14 August 2021 at 15:58:17 UTC, Tejas wrote:

>

If you're willing to help further, would you please tell me why there is a GC allocation in the code below that uses emplace? Will such code truly work if GC is never linked in the program?

https://run.dlang.io/is/XEc2WJ

onlineapp.d(26): Error: `@nogc` function `D main` cannot call non-@nogc function `core.memory.GC.stats`

Looks like GC.stats uses the GC.

Then why is it (0,0,0) the first time it gets used?

I actually stole this from here and assumed the author knew what he was doing.

FWIW, using DRT-gcopt=verbose:2 gives grand total GC time as 0 so maybe there is something up with GC.stats only.

August 15, 2021

On Sunday, 15 August 2021 at 00:15:32 UTC, Ali Çehreli wrote:

>

On 8/14/21 4:41 AM, Tejas wrote:

>

[...]
exception
[...]

So, there would be many exception objects one for each place that an exception can be thrown. Functions like enforce() would have to take a reference to the exception object that is associated with that local scope.

[...]

I just want @nogc exceptions ;_;

Please tell me that the GC.stats thing I posted is irrelevant so that I can go back to using emplace.

I wanted to allocate a class on the heap without being forced to use templates... but I guess that simply isn't possible(I know I can use mixin, but that's even worse).

August 15, 2021

On Sunday, 15 August 2021 at 02:09:08 UTC, Tejas wrote:

>

On Sunday, 15 August 2021 at 00:15:32 UTC, Ali Çehreli wrote:

>

On 8/14/21 4:41 AM, Tejas wrote:

>

[...]
exception
[...]

So, there would be many exception objects one for each place that an exception can be thrown. Functions like enforce() would have to take a reference to the exception object that is associated with that local scope.

[...]

I just want @nogc exceptions ;_;

Please tell me that the GC.stats thing I posted is irrelevant so that I can go back to using emplace.

I wanted to allocate a class on the heap without being forced to use templates... but I guess that simply isn't possible(I know I can use mixin, but that's even worse).

In @nogc code, of course, like how for stack allocation there's simple scope c = new C(ctor args...), but to allocate on heap is auto c = heapAlloc!(A, ctor args...). I wanted to remove the !

August 15, 2021

On Sunday, 15 August 2021 at 02:09:08 UTC, Tejas wrote:

>

On Sunday, 15 August 2021 at 00:15:32 UTC, Ali Çehreli wrote:

>

On 8/14/21 4:41 AM, Tejas wrote:

>

[...]
exception
[...]

So, there would be many exception objects one for each place that an exception can be thrown. Functions like enforce() would have to take a reference to the exception object that is associated with that local scope.

[...]

I just want @nogc exceptions ;_;

Please tell me that the GC.stats thing I posted is irrelevant so that I can go back to using emplace.

I wanted to allocate a class on the heap without being forced to use templates... but I guess that simply isn't possible(I know I can use mixin, but that's even worse).

You can't really have @nogc allocated Exception without circumventing the type system. Personally I gave up on @nogc just because of how inconvenient it is.

Regarding the broader topic, at Sociomantic, we had pre-allocated Exception. After years working there, I grew to see the throw new Exception you see everywhere as an anti-pattern.

Exceptions aren't convenient to use. At the very least, we should have a way to print a formatted message, however nothing currently offers this.

A simple way to achieve this is the following:
https://github.com/bosagora/agora/blob/113c89bd63048a7b98b8e9a2a664bd0eb08ebc84/source/agora/common/Ensure.d

The gist of it is a statically allocated Exception (via module ctor) that is thrown by our ensure method. This is not pure unfortunately (because accessing a global mutable is not pure), and not @nogc (because snformat / formattedWrite aren't @nogc), but it doesn't allocate.

As Ali mentioned, having a single Exception in the program breaks Exception chaining. But Exception chaining is one of the lowest ROI feature of D: It took a lot of effort to implement correctly, and is barely, if at all, used. There have even been talks (involving Walter) of deprecating it.

If your goal is to never link in the GC, then you can barely use Phobos. If your goal is to never use the GC, I would say, why ? Using the GC for long-lived object and/or infrequent allocation doesn't hurt. GC collections are only triggered when you call new, so as long as you keep your critical path free of allocations, you're in the clear.

Another little piece of code you might find interesting: https://github.com/sociomantic-tsunami/ocean/blob/adb31c84baa2061d07aaa0cb7a7d14c3cc98309b/src/ocean/core/Test.d#L305-L340

This uses a built-in method (gc_stats) which predates GC.stats and doesn't work in D2, but I'm sure you'll easily figure out how to adapt it :)

« First   ‹ Prev
1 2 3