Skip to content

Dynlink library: use a global lock#11032

Merged
Octachron merged 9 commits intoocaml:trunkfrom
Octachron:locked_dynlink_with_test
Mar 1, 2022
Merged

Dynlink library: use a global lock#11032
Octachron merged 9 commits intoocaml:trunkfrom
Octachron:locked_dynlink_with_test

Conversation

@Octachron
Copy link
Copy Markdown
Member

This PR tweaks the current implementation of the Dynlink implementation to allow loading plugins from any domains by adding a global lock to all the library entry point.

There is some subtlety on the bytecode side, where we need to release the lock to run the dynlinked module initialization code, but reacquire it before editing the global Symtable.

The test is probably excessive: it loads a 4 layers of interdependent plugins and spawns 20 domains over its course. However, it is automatically generated and can be reduced. The test generator is currently committed, but I am not sure that we want to keep it in tree.

with_lock (fun () ->
if not !inited then begin
P.init ();
default_available_units ();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

default_available_units must only be called when holding the lock. Ideally this would be documented with:

  1. a documentation comment on the function
  2. or a name change (default_available_units_with_lock?)
  3. we could even think of an overkill API where with_lock provides an abstract token type (a proof of locking) and default_available_unit requirs it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

For 3, a simpler API could be to have with_lock provide access to the global state of Dynlink (in a record?).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like this suggestion, it would also make good progress towards a better, multicore-friendly API.

let with_lock lock f =
Mutex.lock lock;
Fun.protect f
~finally:(fun () -> Mutex.unlock lock)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Remark for later: I'd like to have this in Mutex! In the meantime, this could go in Misc.

Symtable.patch_object code compunit.cu_reloc;
Symtable.check_global_initialized compunit.cu_reloc;
Symtable.update_global_table ()
let clos = with_lock lock (fun () ->
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you comment on why you decided to widen the critical section here? Did we miss a protected-state access in the non-locked sections previously, or is it for some other reason?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The section was widened to protect

        let old_state = Symtable.current_state () in
        ...
        if priv then Symtable.hide_additions old_state

from a concurrent write on the Symtable. And also because it seemed simpler to be as coarse as possible with the lock.


let unsafe_get_global_value ~bytecode_or_asm_symbol =
with_lock (fun () -> P.unsafe_get_global_value ~bytecode_or_asm_symbol)
with_lock (fun _ -> P.unsafe_get_global_value ~bytecode_or_asm_symbol)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why do we need to lock here? Is it related to the use of Symtable in the bytecode version?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, the bytecode version reads the Symtable.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe a comment could be nice.

We could also pass the lock to P.unsafe_get_global_value. No strong opinion, I just found that it was strange to lock and then ignore the state.

Copy link
Copy Markdown
Member

@gasche gasche left a comment

Choose a reason for hiding this comment

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

This looks good, thanks a lot! I left a couple minor comments, you can do whatever.


let unsafe_get_global_value ~bytecode_or_asm_symbol =
with_lock (fun () -> P.unsafe_get_global_value ~bytecode_or_asm_symbol)
with_lock (fun _ -> P.unsafe_get_global_value ~bytecode_or_asm_symbol)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

maybe a comment could be nice.

We could also pass the lock to P.unsafe_get_global_value. No strong opinion, I just found that it was strange to lock and then ignore the state.

@gasche
Copy link
Copy Markdown
Member

gasche commented Feb 25, 2022

@Octachron I think this would deserve a Changes entry. (It's a non-trivial piece of stdlib adaptation.)

@Octachron Octachron force-pushed the locked_dynlink_with_test branch from 97a7b4d to ee68e9e Compare February 25, 2022 11:53
@gasche
Copy link
Copy Markdown
Member

gasche commented Feb 25, 2022

@Octachron you made some changes and the CI is now green. Do you want to merge now? (Otherwise you should say when it's ready, possibly by using the merge-me label.)

@Octachron
Copy link
Copy Markdown
Member Author

I am mostly left wondering if I want to keep the test generator in tree or reduce the parameters of the test. I will merge once I decide that not changing anything is fine.

@xavierleroy xavierleroy added this to the 5.0.0 milestone Feb 25, 2022
@Octachron
Copy link
Copy Markdown
Member Author

I have reduced the number of plugins by two (while checking that the test still fail if the bytecode doesn't acquire the lock when modifying the global Symtable.t). The test takes 3 second with my test machine, which seems fine; and the PR adds now less than 50 files.
That seems to be enough test self-moderation for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants