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

Add SystemProperties #3924

Merged
merged 19 commits into from
Nov 21, 2024
Merged

Conversation

massimosiani
Copy link
Contributor

Addresses #3313.

@djspiewak
Copy link
Member

Thank you for looking at this! An interesting question for us to answer though is what we want to do with Native and JavaScript. This is a bit different from Env, which works on all three platforms (with the exception of in-browser JS).

@armanbilge
Copy link
Member

Native and JavaScript

They both support System properties. It's basically just a global mutable map.

@durban
Copy link
Contributor

durban commented Jan 4, 2024

A question: the getAndSet, modify, and similar methods look like the ones on Ref. However, here they are not atomic, right? So having them could be somewhat misleading. (While having only get and set would make it clear, that there is no atomicity for modification.)

@massimosiani
Copy link
Contributor Author

I would agree. TBH, I wondered whether we should try and make them atomic, but that's probably not something you'd expect by system props.

@durban
Copy link
Contributor

durban commented Jan 5, 2024

I don't think we can make them atomic. Or do you know a way?

@massimosiani
Copy link
Contributor Author

Right, not sure how could be made atomic. Citing Arman, it's basically just a global mutable map.
I'll remove getAndSet and getAndUpdate but I think that update is still useful if we make clear that there are no guarantees about concurrent modifications. It's a handy wrapper for a get followed by set. I also like modify as a wrapper but would like to hear other opinions.

@armanbilge
Copy link
Member

I think that update is still useful if we make clear that there are no guarantees about concurrent modifications

Unfortunately I think it's still confusing. Anyway, I expect 99% of usecases to just be reading system properties. APIs that require setting/updating system properties seem really dicey 🙄

@durban
Copy link
Contributor

durban commented Jan 6, 2024

I'd just go with get, set and remove. If that's the functionality offered by the platform, and we can't provide atomicity, its best simply to wrap those.

@armanbilge
Copy link
Member

armanbilge commented Jan 6, 2024

Similar to Env, we can also add something to list/iterate the keys/entries.

*/
def unset(key: String): F[Unit]

def entries: F[Properties]
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I like returning a mutable object here. (I think we usually try to avoid that?)

Copy link
Member

@armanbilge armanbilge Jan 10, 2024

Choose a reason for hiding this comment

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

For Env we decided to return an Iterable[(String, String)], but that was because of case-sensitivity issues which I don't think apply here. So probably makes most sense to return an immutable Map[String, String].

@armanbilge armanbilge added this to the v3.6.0 milestone May 20, 2024
@armanbilge armanbilge closed this May 20, 2024
@armanbilge armanbilge reopened this May 20, 2024
Copy link
Contributor

@durban durban left a comment

Choose a reason for hiding this comment

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

I think the entries is not an atomic snapshot of the system properties. it would be nice if we could do that, but I don't think it's possible with the available JDK API.

F.delay(Option(System.getProperty(key))) // thread-safe

def set(key: String, value: String): F[Unit] =
F.void(F.delay(System.setProperty(key, value)))
Copy link
Contributor

Choose a reason for hiding this comment

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

If I'm reading the JDK source correctly, this eventually calls a synchronized method. So, technically, it should be F.blocking. I don't know if we care about this though...

Copy link
Member

Choose a reason for hiding this comment

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

Yes, that's right. I think we should do that.

Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
F.void(F.delay(System.setProperty(key, value)))
F.void(F.blocking(System.setProperty(key, value)))

@nowarn3("cat=deprecation")
def entries: F[Map[String, String]] = {
import scala.collection.JavaConverters._
F.delay(Map.empty ++ System.getProperties().asScala)
Copy link
Contributor

Choose a reason for hiding this comment

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

Similarly, I think this calls into a Collections.synchronizedSet, so technically should be blocking. (Although this should be uncontended, I think. So we probably care even less...)

for {
_ <- Prop[IO].set("some property", "the value")
props <- Prop[IO].entries
assertion <- IO(props mustEqual System.getProperties())
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a test failure here, I think it's just because we're comparing a Scala Map with a Java hashtable.


import scala.collection.immutable.Map

trait Prop[F[_]] { self =>
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should make this sealed, just for future-proofing?

Copy link
Member

Choose a reason for hiding this comment

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

On the other hand, this prevents mocking. Our current practice is not really to sealed anything, for better or for worse 🤷

@TonioGela
Copy link
Member

[BIKESHEDDING] I'm late to the party to comment probably, but I fear that Prop[F] as a name may clash with scalacheck or one of its integrations.

@armanbilge
Copy link
Member

armanbilge commented Jul 15, 2024

but I fear that Prop[F] as a name may clash with scalacheck or one of its integrations.

I actually also dislike the name Prop[F], without even considering the Scalacheck issues (good point).

The reason that I dislike it is because this API is very specifically abstracting over the (global singleton) system properties, not the generic JDK Properties datatype.

If we want to keep the name short we could rename to SysProps[F] perhaps. Or just go for SystemProperties[F] which I think is my preference.

@armanbilge
Copy link
Member

I think the entries is not an atomic snapshot of the system properties. it would be nice if we could do that, but I don't think it's possible with the available JDK API.

The JDK suggests there is some notion of "bulk" operations.

    /**
     * Properties does not store values in its inherited Hashtable, but instead
     * in an internal ConcurrentHashMap.  Synchronization is omitted from
     * simple read operations.  Writes and bulk operations remain synchronized,
     * as in Hashtable.
     */

https://github.com/openjdk/jdk/blob/388fcf03c02c41bb690733e8565642c24ead56e0/src/java.base/share/classes/java/util/Properties.java#L161-L167

I am still trying to figure out what the bulk operations are exactly. If serializing the properties to a stream is one such bulk operation, perhaps we can use that to make an atomic copy first ... 🤔

@armanbilge armanbilge marked this pull request as draft July 15, 2024 19:28
@armanbilge
Copy link
Member

I think the entries is not an atomic snapshot of the system properties. it would be nice if we could do that, but I don't think it's possible with the available JDK API.

It is possible, I think I was able to fix it. Thanks for catching :)

import scala.collection.JavaConverters._
val props = System.getProperties
val back = new java.util.HashMap[String, String](props.size())
props.putAll(back)
Copy link
Member

Choose a reason for hiding this comment

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

Aw crap, I think I got this backwards 😕 it should be back.putAll(props) but then it's no longer atomic which was the point of copying. Hmm.

@massimosiani
Copy link
Contributor Author

Or just go for SystemProperties[F] which I think is my preference.

That's my favorite too.

@armanbilge
Copy link
Member

After going through a few iterations, I think we should just stick to get/set/clear.

Technically Properties extends Hashtable, and it is possible to implement a MapRef for Hashtable. But actually it extends Hashtable[Object, Object], and the Properties-specific APIs add special handling for non-String values and defaults. Anyway, it's a bit of a mess, and I really don't see a compelling usecase for atomic modification of Properties.

@armanbilge armanbilge changed the title Feature/prop Add SystemProperties Nov 21, 2024
@armanbilge armanbilge marked this pull request as ready for review November 21, 2024 17:53
@djspiewak djspiewak merged commit 8c305d7 into typelevel:series/3.x Nov 21, 2024
29 of 33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants