Re: [PR] [JSPWIKI-1178] Address potential deadlock with JDK 21 Virtual Threads. [jspwiki]

2023-10-12 Thread Juan Pablo Santos Rodríguez
Hi Murray,

to add some context to the PR, from what I understand, the important
thing about the Synchronizer class, is more on the switch to use
ReentrantLock instead of synchronized blocks, than the code
readability/simplification. The Synchronizer class just captures an
idiom throughout the code, but what's important is the change being
carried, using ReentrantLocks instead of synchronized.

As for the why of above, for the time now, we are not going to use
virtual threads directly, but we can benefit from it indirectly when
running under JDK21, request dispatching and I/O are two examples
where we should see performance gains. And to that we should move away
from synchronized sections (not from *every* synchronized section, a
bit on that below). JEP 444 [#1] introduced Virtual Threads so is a
good starting point to get an overall feeling of how they work and
what they strive to deliver. Quoting from it:

> [...] There are two scenarios in which a virtual thread cannot be unmounted 
> during blocking operations because it is pinned to its carrier:
>
>When it executes code inside a synchronized block or method, or
>When it executes a native method or a foreign function.
>
> Pinning does not make an application incorrect, but it might hinder its 
> scalability. If a virtual thread performs a blocking operation such as I/O
> or BlockingQueue.take() while it is pinned, then its carrier and the 
> underlying OS thread are blocked for the duration of the operation. Frequent
> pinning for long durations can harm the scalability of an application by 
> capturing carriers.
> The scheduler does not compensate for pinning by expanding its parallelism. 
> Instead, avoid frequent and long-lived pinning by revising synchronized
> blocks or methods that run frequently and guard potentially long I/O 
> operations to use java.util.concurrent.locks.ReentrantLock instead. There is 
> no
> need to replace synchronized blocks and methods that are used infrequently 
> (e.g., only performed at startup) or that guard in-memory operations.
> As always, strive to keep locking policies simple and clear.
> [...]
> In a future release we may be able to remove the first limitation above, 
> namely pinning inside synchronized.[...]

(the carrier is the platform [normal / real / underlying] thread)

So, generally, switching to ReentrantLock should be the way to go to
benefit from virtual threads. The case you highlight, as per the jep,
could be left as a it is, but I don't see much difference on
performance there either. Most probably, the synchronized keyword is
being translated under the hoods to the code generated by the
synchronized class; I haven't seen the generated bytecode, so I may be
completely wrong here, it's a gut feeling. I don't have an strong
opinion whether to use one over the other (I'll defer Arturo to that),
but since it seems that it could fit the idiom, and was done in the
PR, which carries some significant work, I've lgtm'ed that when
reviewing - but I'm more than happy to be corrected if it isn't a good
solution.

Regarding the increase in memory usage, as per [#2], a fully working
WikiEngine weights around 5.5MB. The attached PR adds, say about, 40
to 60 ReentrantLock, haven't counted them, which are either static or
part of a WikiEngine manager, that is, those locks should not increase
over time. I have no idea about the impact on memory that they will
have, but hopefully we can run that test afterwards and observe how
much that costs to the Engine and iterate over that. I expect that the
memory increase should be tolerable, specially if it allows to benefit
from virtual threads, but again, will be very grateful for ayone
correcting me. And, on the particular case you mention, I have no
strong opinion on which one should be used, I think both of them
should be fine, but happy to be corrected if needed, I just wanted to
put some context on the PR.

As a side note, IIRC, the technique you describe for creating
singletons is the same that enums use under the covers to create their
values, so a very clean way to create singletons as of today is
through enums, f.ex.:

public enum IdGenerator {
INSTANCE;
public static getInstance() {
return INSTANCE;
}
[...]
}

should be roughly the same as the example noted on the previous email.


best regards,
juan pablo

[#1] https://openjdk.org/jeps/444
[#2] https://lists.apache.org/thread/cqppw3bmgmthr86718yrhtz93kw3d2h1


On Thu, Oct 12, 2023 at 12:38 AM Murray Altheim  wrote:
>
> Hi,
>
> My use of the synchronized block in WikiEventManager was based on a
> section "Use Double-Checked Locking to Reduce Synchronization" of Craig
> Larman and Rhett Guthrie's book "Java 2: Performance and Idiom Guide"*
> about the use of a synchronized block on singleton classes protected by
> a pair of null checks.
>
> I have been successfully using this approach in my own software designs
> since the 90s, quoting from page 100:
>
>"Consider the example of a

Re: [PR] [JSPWIKI-1178] Address potential deadlock with JDK 21 Virtual Threads. [jspwiki]

2023-10-12 Thread Arturo Bernal
Hi Murray

I'd like to clarify the main goal of my PR, which is to transition from
traditional synchronized blocks to the more modern ReentrantLocks. The
Synchronizer class is essentially a way to standardize this transition
across the codebase.

I agree with Juan Pablo's point about the benefits of switching to
ReentrantLock to make the most of virtual threads, especially with JDK 21
and beyond. The use of ReentrantLock can indeed offer more flexibility and
control, which can be advantageous in high-concurrency scenarios.

As for the performance aspect, while I haven't seen the generated bytecode
either, I believe the performance difference between ReentrantLock and
synchronized is often negligible for most applications. However,
ReentrantLock may offer better scalability, which could be beneficial as we
look to future-proof the code.

Murray, your point about the double-null check for singletons is
well-taken. The synchronized keyword has its merits, especially in simpler
use-cases where the overhead of creating a lock object and handling
exceptions might not be justified.

In summary, I don't have a strong opinion on one over the other for all
scenarios, but I do think that ReentrantLock offers advantages that align
well with the direction Java is taking with virtual threads, also I believe
that ReentrantLocks could offer us more flexibility and scalability as Java
continues to evolve.

Best regards,

Arturo


On Thu, Oct 12, 2023 at 5:09 PM Juan Pablo Santos Rodríguez <
juanpablo.san...@gmail.com> wrote:

> Hi Murray,
>
> to add some context to the PR, from what I understand, the important
> thing about the Synchronizer class, is more on the switch to use
> ReentrantLock instead of synchronized blocks, than the code
> readability/simplification. The Synchronizer class just captures an
> idiom throughout the code, but what's important is the change being
> carried, using ReentrantLocks instead of synchronized.
>
> As for the why of above, for the time now, we are not going to use
> virtual threads directly, but we can benefit from it indirectly when
> running under JDK21, request dispatching and I/O are two examples
> where we should see performance gains. And to that we should move away
> from synchronized sections (not from *every* synchronized section, a
> bit on that below). JEP 444 [#1] introduced Virtual Threads so is a
> good starting point to get an overall feeling of how they work and
> what they strive to deliver. Quoting from it:
>
> > [...] There are two scenarios in which a virtual thread cannot be
> unmounted during blocking operations because it is pinned to its carrier:
> >
> >When it executes code inside a synchronized block or method, or
> >When it executes a native method or a foreign function.
> >
> > Pinning does not make an application incorrect, but it might hinder its
> scalability. If a virtual thread performs a blocking operation such as I/O
> > or BlockingQueue.take() while it is pinned, then its carrier and the
> underlying OS thread are blocked for the duration of the operation. Frequent
> > pinning for long durations can harm the scalability of an application by
> capturing carriers.
> > The scheduler does not compensate for pinning by expanding its
> parallelism. Instead, avoid frequent and long-lived pinning by revising
> synchronized
> > blocks or methods that run frequently and guard potentially long I/O
> operations to use java.util.concurrent.locks.ReentrantLock instead. There
> is no
> > need to replace synchronized blocks and methods that are used
> infrequently (e.g., only performed at startup) or that guard in-memory
> operations.
> > As always, strive to keep locking policies simple and clear.
> > [...]
> > In a future release we may be able to remove the first limitation above,
> namely pinning inside synchronized.[...]
>
> (the carrier is the platform [normal / real / underlying] thread)
>
> So, generally, switching to ReentrantLock should be the way to go to
> benefit from virtual threads. The case you highlight, as per the jep,
> could be left as a it is, but I don't see much difference on
> performance there either. Most probably, the synchronized keyword is
> being translated under the hoods to the code generated by the
> synchronized class; I haven't seen the generated bytecode, so I may be
> completely wrong here, it's a gut feeling. I don't have an strong
> opinion whether to use one over the other (I'll defer Arturo to that),
> but since it seems that it could fit the idiom, and was done in the
> PR, which carries some significant work, I've lgtm'ed that when
> reviewing - but I'm more than happy to be corrected if it isn't a good
> solution.
>
> Regarding the increase in memory usage, as per [#2], a fully working
> WikiEngine weights around 5.5MB. The attached PR adds, say about, 40
> to 60 ReentrantLock, haven't counted them, which are either static or
> part of a WikiEngine manager, that is, those locks should not increase
> over time. I have

[PR] Replace 'size() == 0' with 'isEmpty()' [jspwiki]

2023-10-12 Thread via GitHub


arturobernalg opened a new pull request, #314:
URL: https://github.com/apache/jspwiki/pull/314

   (no comment)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: dev-unsubscr...@jspwiki.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org



Re: [PR] [JSPWIKI-1178] Address potential deadlock with JDK 21 Virtual Threads. [jspwiki]

2023-10-12 Thread Murray Altheim

HI Arturo and Juan Pablo,

I don't disagree with anything either of you have written. I'm all in
favour of the ReentrantLock replacement via a Synchronizer pattern.

I'm only suggesting, as does both Larman/Guthrie and the quoted section
("There is no need to replace synchronized blocks and methods that are
used infrequently (e.g., only performed at startup)..."), that the
double-null-checked synchronized blocks used to protect singletons is
likely still a simpler and better practice, and there'd be little
benefit of a ReentrantLock in those cases. These are only called at
the moment when the singleton is created, i.e., once per session.

Cheers,

Murray

On 13/10/23 06:08, Arturo Bernal wrote:

Hi Murray

I'd like to clarify the main goal of my PR, which is to transition from
traditional synchronized blocks to the more modern ReentrantLocks. The
Synchronizer class is essentially a way to standardize this transition
across the codebase.[...]


...
Murray Altheim= =  ===
http://www.altheim.com/murray/ ===  ===
   = =  ===
In the evening
The rice leaves in the garden
Rustle in the autumn wind
That blows through my reed hut.
   -- Minamoto no Tsunenobu