I decided to play once again with shared and see what 2.032 is capable of. 
Turns out a lot of the previous issues I was having last time are gone, 
however, there are still a few things left which prevent me from rewriting my 
code.

The first issue that jumped to my face straight away was how 'shared const' 
methods are not callable from 'shared' objects.

shared class Foo {
        void bar() const;
}
auto foo = new Foo; // foo is of type shared(Foo)
foo.bar; //  Error: function Foo.bar () shared const is not callable using 
argument types () shared

Considering how 'const' methods can be called from mutable objects, this looks 
like either a bug or a really awkward feature to me. Sending a shared(Foo) to a 
method expecting a shared(const(Foo)) also triggers a similar error from the 
compiler.

The other issue may be an intended feature, but it doesn't sound practical to 
me. Marking a method as shared assumes all used properties in the method's 
scope are also shared. Here is an example to illustrate my point:

class SimpleReader {
   this(LocalFile file) { _stream = new FileInputStream(file); }
   ...
private:
    synchronized void read(ubyte[] buf, long offset) {
        _stream.seek(offset);
        _stream.read(buf);
    }
    FileInputStream _stream;
}

The FileInputStream here is a generic blocking binary stream which is not 
thread-safe by design. The reader is a composite class where every instance has 
its own unique stream instance and use it to implement asynchronous reads over 
the file format it abstracts, which in my case is a specialized read-only 
archive using a lot of random accesses from different threads.

This is where the issue shows its ugly head. The 'synchronized' keyword tags 
the read method as shared, which in itself is quite neat, what is annoying 
however is that it also changes the type of _stream in the method's scope to 
shared(FileInputStream) and therefore triggers compiler errors because 
_stream.seek and _stream.read are not shared:

Error: function FileInputStream.read (ubyte[]) is not callable using argument 
types (ubyte[]) shared

While it may be an attempt to keep shared usage safe, it isn't very practical. 
The stream object here is not shared because it is not thread-safe. While it 
may be used by different threads, it is unique to the reader's context and its 
accesses are synchronized by the reader, the stream should therefore be 
completely oblivious to the fact it is being used by different threads.

Maybe this could be the time to implement an unique qualifier; this is a 
context where having _stream be of type unique(FileInputStream) would solve the 
problem and allow further compiler optimizations. I don't know if it can be 
done with templates, and without any overhead whatsoever. I know I would much 
rather see unique(Foo) than Unique!Foo, and it would allow the use of 'is(foo : 
unique)'.

Furthermore, tagging a method with shared does not make it thread-safe, it may 
however use synchronized within its scope to protect its shared or unique data. 
This may be confusing when calling shared methods vs calling synchronized 
methods; one may think the shared one is not thread-safe and optionally 
synchronize the call, resulting in another monitor being used for nothing, or 
no monitor being used at all:

class Foo {
    shared void bar() {
        // Do stuff with local or immutable data
        synchronized(this) { /* do stuff with shared data */ }
    }
    shared void bar2() {
        // Do stuff on shared data
    }
}

Someone seeing only the prototype of Foo.bar may assume the method is not 
thread-safe and call it as 'synchronized(foo) foo.bar()'. Just like they could 
see the prototype of bar2 and assume it is thread-safe, calling it as 
'foo.bar2()'.

What could be a good design against this sort of misleading behavior?

Phew, that's about enough issues and questions for now :)

Reply via email to