Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic Key API #3485

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open

Basic Key API #3485

wants to merge 6 commits into from

Conversation

dkalinichenko-js
Copy link
Contributor

Basic Capsule Key API.

Adds 'k Key.t, a ghost type representing the ownership of a capsule 'k. Keys can be converted to passwords or accesses using with_password and access. Exceptions raised from those are unwrapped to the original, destroying the key in process.

Mutexes and rw-locks are now created using keys and leak keys on destruction.

Shared keys are not implemented yet, pending discussion how to deal with exceptions.

Copy link
Collaborator

@lpw25 lpw25 left a comment

Choose a reason for hiding this comment

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

A few suggestions, but this looks good.

otherlibs/stdlib_alpha/capsule.ml Outdated Show resolved Hide resolved
@@ -44,12 +44,12 @@
module Name : sig

type 'k t : value mod global portable many uncontended unique
(** A ['k Name.t] represents the identity of a capsule. *)
(** A ['k Name.t] represents an identity of a capsule.
A capsule can have multiple names. *)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think once something has multiple names it probably isn’t an “identity”. I think, with the design you have here, it would be better to move Name.t to be Password.Name.t and describe it as the identity of a password instead.

That would also make it easy to delay creating the name until it is asked for from the password by having the password be mutable and hold a dummy value when created. That will avoid needless contention on the atomic used for allocating names.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also, maybe Password.Id.t rather than Password.Name.t

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Making the password mutable means it can't easily cross contention, though.

Copy link
Collaborator

Choose a reason for hiding this comment

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

We can just assert that it does. You can see it is semantically fine because the mutable part could be a ref inside the capsule the password is associated with, so that the password itself constituted permission to access it. It's just that we can't use the Password API inside the definition of the Password API.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What about shared passwords?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, due to val shared : 'k t @ local -> 'k Shared.t @ local @@ portable we have to use the atomic everywhere (and we need that function to write interfaces general over whether your data is guarded by a mutex or by a rw-lock).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess, we could track whether the shared password is sourced from a normal password or created shared, but that seems overly complicated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, it's pretty easy to track, so why not.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I wasn’t going to bother storing the atomic in the locks, my suggestion was to have ids only be the ids of passwords not of capsules generally, so we’d just stack allocate a fresh atomic cell for each new password.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. It feels somewhat wrong that just locking a mutex would require a new allocation. I guess we can compensate by adding another version of with_lock that provides Access.t instead.

otherlibs/stdlib_alpha/capsule.ml Outdated Show resolved Hide resolved
testsuite/tests/capsule-api/key.ml Outdated Show resolved Hide resolved
@dkalinichenko-js dkalinichenko-js added the drf Data race freedom label Jan 17, 2025
@dkalinichenko-js
Copy link
Contributor Author

Updated to support Capsule.Key.with_password_local, removed redundant Capsule.Key.with_password{,_local} functions.

@dkalinichenko-js
Copy link
Contributor Author

Updated to only initialize passwords on Password.name t. Both normal and shared passwords are backed by the same type, but normal passwords use unsynced access operations while shared passwords use atomics.

@dkalinichenko-js
Copy link
Contributor Author

Updated the API to handle shared keys, Data.Shared, and freezing rw-locks. The diff is quite large, so I recommend reviewing this from the beginning.

Copy link
Collaborator

@lpw25 lpw25 left a comment

Choose a reason for hiding this comment

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

I've reviewed everything except the new tests. Will review them later/tomorrow.

otherlibs/stdlib_alpha/capsule.mli Outdated Show resolved Hide resolved
otherlibs/stdlib_alpha/capsule.mli Outdated Show resolved Hide resolved
otherlibs/stdlib_alpha/capsule.mli Outdated Show resolved Hide resolved
otherlibs/stdlib_alpha/capsule.mli Outdated Show resolved Hide resolved

val create_with_rwlock : unit -> Rwlock.packed @@ portable
(** [create_with_rwlock ()] creates a new capsule with an associated reader-writer lock. *)

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it is probably still worth leaving these in as convenience functions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll add them to the version in the tree.

let[@inline] with_password (type k) k f =
let pw : k Password.t = Password.unsafe_mk () in
try f pw, k with
| Encapsulated (id, data) as exn ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess this should also catch Encapsulated_shared since the password might get converted to a shared password. The docs should also be updated to reflect this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed shared instead, since it's either useless or unsound in presence of fork_join.

| x -> Rw.unlock t.rwlock; x
| exception exn ->
t.poisoned <- true;
t.state <- Frozen;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This needs to set it to Posioned rather than Frozen as the exception that is being leaked may mutate the data from the capsule.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

| x -> Rw.unlock t.rwlock; x
| exception exn ->
(* Here we are not poisoning the RwLock, see [capsule.mli] for explanation *)
let exn =
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is this done before releasing the lock here, but after releasing the lock in with_lock?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Inconsistently copied from Mutex. I think it is a mistake to do it after releasing the lock: there may be another exception raised that modifies the data in the capsule. I changed all functions to convert the exception before the lock is released.

| None -> exn)
| _ -> exn
in
t.state <- Frozen;
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is probably worth adding a comment here about why this racy write is ok according to the memory model. The reason it is ok is that:

  1. All threads which race to write here are all racing to write Frozen and the only other write to this field in the program was the initial one that set it to Open.
  2. All threads which race to read here do not distinguish between Open and Frozen so do not care which value they see.
  3. The only reads which do care about the difference between Open and Frozen are ones that happen whilst holding the lock for writing -- that means there is a happens before relationship between them and all the writes, so they will reliably see the Frozen state.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added a comment.

| false ->
t.poisoned <- true;
raise Poisoned
| Open | Frozen ->
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should fail with raise Frozen if the lock was already frozen. You cannot safely transition from Frozen to Poisoned.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
drf Data race freedom
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants