Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

Conversation

yglukhov
Copy link
Contributor

This PR introduces optional monitors feature.
The whole idea is that Object doesn't contain __monitor field anymore.
TypeInfo_Class will hold a monitor offset, if a class has one (by marking class declaration with @monitor attribute), or 0.
Monitor lookup is done with a hash map, if monitorOffset is 0.
The hash map is protected by a primitive RW spin lock.
Monitor finalization will only lookup for monitors, if a monitor was allocated at least once for the type of object being finalized.

If user wants to synchronize over some class, then he will probably want to add a @monitor attribute to it to gain performance bonus (no hash lookup will occur). This attribute will introduce an invisible __monitor field.

DMD counterpart: dlang/dmd#3547
discussion: http://forum.dlang.org/thread/xpliectmvwrwthamquke@forum.dlang.org

@MartinNowak
Copy link
Member

Can we also move blank synchronized statements to the hash table?

@yglukhov
Copy link
Contributor Author

As far as i understood, expression-less synchronized are working in another way. They are actually lowered to entering/exiting a critical section declared static shared in place of synchronized. So there's actually no need for those to be in the table. However, maybe someone who understands this completely should add a comment.

@MartinNowak
Copy link
Member

Otherwise I'd favor deprecating synchronized on classes without @monitor.

@MartinNowak
Copy link
Member

They are similar in that they both use global mutexes.

@yglukhov
Copy link
Contributor Author

But there's no need to look em up. They are global and referenced directly. And they are not tied to any object or whatever. Maybe i misunderstood you on this one...

@MartinNowak
Copy link
Member

Ah sorry, they should also be looked up, because as it's done right now the compiler has to know the size of the mutex being used. https://github.com/D-Programming-Language/dmd/blob/master/src/target.c#L127

@MartinNowak
Copy link
Member

That's a little unrelated though.
Won't a global hash table for non-@monitor classes turn synchronize into an impure statement?

@dnadlinger
Copy link
Contributor

@MartinNowak: Why would it? The fact that the implementation stores part of the object state in global memory doesn't mean that the semantics of synchronized are changed.

@MartinNowak
Copy link
Member

Well previously synchronized didn't need any global state, after the change this program would no longer compile.

@MartinNowak
Copy link
Member

IMO we should deprecate the old behavior instead of adding a flawed compat layer.

@dnadlinger
Copy link
Contributor

As I mentioned previously, I'd unconditionally support a change towards the explicit-only design if it were not for backwards compatibility, for reasons of simplicity and predictability of performance characteristics. However, I've yet to see a convincing argument against the design proposed here solely on account of correctness. I think we can agree that, if implemented correctly, the change proposed here does not modify observable program semantics. Thus, either the use of synchronized can remain pure (even though it may be backed by an impure implementation internally), or monitors currently are a purity loophole that needs to be fixed.

Now, from a cursory look at the actual diff of the pull request, it seems as though it indeed removes pure from several monitor operations. But if this causes code no longer to compile, this is an implementation problem that can be avoided using suitable casts and not a fundamental design flaw.

@MartinNowak
Copy link
Member

I'd unconditionally support a change towards the explicit-only design if it were not for backwards compatibility

That's why I said deprecating not removing, i.e. 12-18 month.

@schveiguy
Copy link
Member

Won't a global hash table for non-@monitor classes turn synchronize into an impure statement?

A mutex is by definition shared.

Note that monitors are already special. For example, immutable or const objects can be locked, even though the monitor is modified and is part of the object.

@yglukhov
Copy link
Contributor Author

Now, from a cursory look at the actual diff of the pull request, it seems as though it indeed removes pure from several monitor operations. But if this causes code no longer to compile, this is an implementation problem that can be avoided using suitable casts and not a fundamental design flaw.

I tried using synchronized statement in a pure function and it worked. Also i see no reason not to. synchronized statement is lowered to function calls _d_monitorenter and _d_monitorexit which are extern(C) and impure.

{
cs = cast(Monitor *)calloc(Monitor.sizeof, 1);
assert(cs);
pthread_mutex_init(&cs.mon, &_monitors_attr);
setMonitor(h, cs);
cs.refs = 1;
cs = null;
typeid(h).m_flags |= TypeInfo_Class.ClassFlags.hasAllocatedMonitors;
Copy link
Member

Choose a reason for hiding this comment

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

Let the compiler set this flag instead of doing it lazily.
TypeInfo should be read-only.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The idea behind this is to skip monitor finalization for types that never have a monitor. The flag is used in rt_finalize2. Otherwise all types that do not have explicit monitors would suffer a pointless hash-table lookup on finalization.

Copy link
Member

Choose a reason for hiding this comment

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

There is still the read-only issue and flagging a type when you actually want to flag an instance isn't optimal either.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, it does look like a hack (or should we call it "implementation specifics"? :). Does it really has to be that read-only? Of course, any other ideas of how to flag an object without adding a field to it are welcome.

Copy link
Member

Choose a reason for hiding this comment

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

You don't flag the object but it's type and yes I'd like to move TypeInfo to the read-only segment.

Copy link
Member

Choose a reason for hiding this comment

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

Ah there is a data-race too, here is the writer.

@MartinNowak
Copy link
Member

I'm still uneasy about the whole thing, we're adding a somewhat big machinery to support something we want to get rid of whereas deprecation will took longer but doesn't require an intermediate solution.

@yglukhov
Copy link
Contributor Author

Well, i can understand you with that. But the "intermediate" part of the solution is not so big. Specifically the part that involves monitor creation/lookup.
What i am afraid of is if we decide to go the "deprecation" way, the question with signals/slots may take ages to discuss. And we can't start deprecation with this question unresolved.

@@ -1845,7 +1840,6 @@ extern (C) void _d_monitordelete(Object h, bool det)
{
_d_monitor_devt(m, h);
_d_monitor_destroy(h);
setMonitor(h, null);
Copy link
Member

Choose a reason for hiding this comment

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

Not seeing it to null when a class has an embedded monitor seems wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's done in _d_monitor_destroy.

@MartinNowak
Copy link
Member

What i am afraid of is if we decide to go the "deprecation" way, the question with signals/slots may take ages to discuss. And we can't start deprecation with this question unresolved.

Well std.signal is terribly limited, for a good replacement we need weak ptrs though. And std.signal synchronizes on the object anyhow, so you'll want @monitor too.

_d_monitordelete(cast(Object) p, det);
}
}
else if (pc.m_flags & TypeInfo_Class.ClassFlags.hasAllocatedMonitors)
Copy link
Member

Choose a reason for hiding this comment

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

And this is the reader of the data-race.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If i understand this correctly, the flag is set in the monitor creation code, where finalization can not occur. So it kinda looks like a race condition, but in this case these reads/writes do not overlap. Even if they do, and finalization reads m_flags without hasAllocatedMonitors set, that means that no monitor was allocated for the object being finalized. Please correct me if I'm wrong.

Copy link
Member

Choose a reason for hiding this comment

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

The problem is that finalization can occur while your setting the bit. To guarantee that no word tearing occurs, use atomicWrite!(msync.raw) and atomicRead!(msync.raw). But as I said, we must not mutate the TypeInfo anyhow.

@dnadlinger
Copy link
Contributor

Oh, and there would be no reason to migrate argument-less synchronized blocks to the hash table, as there is no need for indirection there in the first place. It's not like you could instantiate multiple copies of a piece of code at runtime, much unlike for classes, where you can have an unknown number of object instances and need some way to associate a lock to each of them.

@MartinNowak
Copy link
Member

Oh, and there would be no reason to migrate argument-less synchronized blocks to the hash table, as there is no need for indirection there in the first place.

I just brought it up because currently the compiler has to know the size of the druntime muted. So I wanted to move this completely to druntime and the easiest solution is to use a hash table from code address to mutex. So it looked quite similar but let's no longer discuss it here.

@yglukhov
Copy link
Contributor Author

yglukhov commented Jun 4, 2014

I've been thinking about read-only typeinfos, and it seems like that's not currently possible, because you can synchronize over typeinfo objects, and it requires writing a monitor ptr inside the object. So my approach makes only 1 bit writable, instead of current 64 =).

class A{}
unittest
{
    A a = new A();
    syncronized(typeinfo(a)) { /* smth */ }
}

@MartinNowak
Copy link
Member

because you can synchronize over typeinfo objects

Which is the broken behavior you're about to fix.
I still don't see the compelling argument to add all this hash table stuff when we can deprecate monitors for classes without @monitor. The latter is a clean approach which admittedly takes a little longer.

@dnadlinger
Copy link
Contributor

The ability to make typeinfo read-only is indeed a good argument for dropping monitors by default, unless I'm missing some other reason why there needs to be write access. Using a typeinfo instance for synchronization is an anti-pattern anyway…

@yglukhov
Copy link
Contributor Author

Rebased.

@quickfur
Copy link
Member

This is failing to merge again, please rebase (again).

@MartinNowak
Copy link
Member

As mentioned here I think we should go for a UDA and properly deprecate the old behavior.
Thanks @yglukhov for your initiative but I'll close this pull for now.

@MartinNowak MartinNowak closed this Oct 4, 2014
@@ -1239,8 +1241,17 @@ extern (C) void rt_finalize2(void* p, bool det = true, bool resetMemory = true)
while ((c = c.base) !is null);
}

if (ppv[1]) // if monitor is not null
if (cast(int)pc.monitorOffset != -1)
Copy link
Member

Choose a reason for hiding this comment

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

Looks like monitorOffset can never be 0 because that's where the vtable is.
Therefor use 0 to detect unallocated monitors so we can put the class initializer into bss.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants