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

8288140: Avoid redundant Hashtable.get call in Signal.handle #9100

Conversation

turbanoff
Copy link
Member

@turbanoff turbanoff commented Jun 9, 2022

Signal.Handler oldHandler = handlers.get(sig);
handlers.remove(sig);

Instead of separate Hashtable.get/remove calls we can just use value returned by remove,
It results in cleaner and a bit faster code.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8288140: Avoid redundant Hashtable.get call in Signal.handle

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk pull/9100/head:pull/9100
$ git checkout pull/9100

Update a local copy of the PR:
$ git checkout pull/9100
$ git pull https://git.openjdk.org/jdk pull/9100/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 9100

View PR using the GUI difftool:
$ git pr show -t 9100

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/9100.diff

@bridgekeeper
Copy link

bridgekeeper bot commented Jun 9, 2022

👋 Welcome back aturbanov! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jun 9, 2022

@turbanoff The following label will be automatically applied to this pull request:

  • core-libs

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

@openjdk openjdk bot added the core-libs core-libs-dev@openjdk.org label Jun 9, 2022
@turbanoff turbanoff changed the title [PATCH] Avoid redundant Hashtable.get call in Signal.handle 8288140: Avoid redundant Hashtable.get call in Signal.handle Jun 9, 2022
@openjdk openjdk bot added the rfr Pull request is ready for review label Jun 9, 2022
@mlbridge
Copy link

mlbridge bot commented Jun 9, 2022

Webrevs

Comment on lines 177 to 180
Signal.Handler oldHandler = handlers.remove(sig);
if (newH == 2) {
handlers.put(sig, handler);
}
Copy link
Contributor

@dmlloyd dmlloyd Jun 9, 2022

Choose a reason for hiding this comment

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

If you made this change instead:

Suggested change
Signal.Handler oldHandler = handlers.remove(sig);
if (newH == 2) {
handlers.put(sig, handler);
}
Signal.Handler oldHandler;
if (newH == 2) {
oldHandler = handlers.put(sig, handler);
} else {
oldHandler = handlers.remove(sig);
}

Wouldn't you be able to remove the entire synchronized block?

Copy link
Member

Choose a reason for hiding this comment

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

Hello David, I suspect you mean handlers.put(sig, handler) and not handlers.replace(...)? And yes, I think what you suggest will help remove the synchronized block here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oops, yes you are correct!

Copy link
Contributor

Choose a reason for hiding this comment

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

Hi,
I think the synchronized block was redundant already in original code. Since the entire handle method is static synchronized and it is the only method that modifies the handlers and signals maps.
But even with so much redundant synchronization, the Signal class is not without races. There are multiple possible races in dispatch(int number) method which is called from native code to dispatch a signal:

  • race no. 1: dispatch method (not synchronized) performs 2 independent lookups into signals and handlers maps respectively, assuming their inter-referenced state is consistent. But handle method may be concurrently modifying them, so dispatch may see updated state of signals map while seeing old state of handlers map or vice versa.
  • race no. 2: besides signals and handlers there is a 3rd map in native code, updated with handle0 native method. Native code dispatches signals according to that native map, forwarding them to either native handlers or to dispatch Java method. But handle method may be modifying that native map concurrently, so dispatch may be called as a consequence of updated native map while seeing old states of signals and handlers maps.

I'm sure I might have missed some additional races.

How to fix this? Is it even necessary? I think that this internal API is not used frequently so this was hardly an issue. But anyway, it would be a challenge. While the two java maps: handlers and signals could be replaced with a single map, there is the 3rd map in native code. We would not want to make dispatch method synchronized, so with careful ordering of modifications it is perhaps possible to account for races and make them harmless...
WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Well, studying this further, I think some races are already accounted for and are harmless except one. The handle method 1st attempts to register handler in native code via call to handle0 and then updates the signals map.. As soon as native code registers the handler, dispatch may be called and the lookup into signals map may return null which would cause NPE in the following lookup into handlers map. So there are two possibilities to fix this:

  • guard for null result from singnals lookup, or
  • swap the order of modifying the signals map and a call to handle0 native method. You could even update the signals map with .putIfAbsent() to avoid multiple reassignment with otherwise equal instances. This may unnecessarily establish a mapping for a Signal the can later not be registered, so you can remove it from the signals map in that case to clean-up.
    The possible case when the lookup into handlers map returns null Handler is already taken into account, but there is a possible case where a NativeHandler is replaced with a java Handler implementation and dispatch is therefore already called, but handlers map lookup still returns a NativeHandler. In that case the NativeHandler::handle method will throw exception. To avoid that, NativeHandler::handle could be an empty method instead.

Copy link
Member Author

Choose a reason for hiding this comment

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

I tried to reproduce this possible problem via jcstress, but never caught this race.

package org.openjdk.jcstress.samples.jmm.basic;

import org.openjdk.jcstress.annotations.*;
import org.openjdk.jcstress.infra.results.ZZZZZZZZ_Result;
import sun.misc.Signal;
import sun.misc.SignalHandler;

import java.io.IOException;

import static org.openjdk.jcstress.annotations.Expect.*;

@JCStressTest
@Outcome(id = "false, false, false, false, false, false, false, false", expect = ACCEPTABLE, desc = "All results are ok")
@Outcome(id = ".*", expect = ACCEPTABLE_INTERESTING, desc = "All results are ok")
@State
public class BasicJMM_11_Signal {

    /*
        How to run this test:
            $ /home/turbanoff/.jdks/liberica-18.0.1.1/bin/java -jar jcstress-samples/target/jcstress.jar -t BasicJMM_11_Signal -v
            $ /home/turbanoff/Projects/official_jdk/build/linux-x86_64-server-fastdebug/jdk/bin/java -jar jcstress-samples/target/jcstress.jar -t BasicJMM_11_Signal -v
            $ /home/turbanoff/Projects/jdk2/build/linux-x86_64-server-release/jdk/bin/java -jar jcstress-samples/target/jcstress.jar -t BasicJMM_11_Signal -v
     */

    private static final Signal[] signals = new Signal[]{
            new Signal("URG"),
//            new Signal("USR2"),
//            new Signal("ALRM"),
//            new Signal("USR1"),
            new Signal("CHLD"),
            new Signal("XFSZ"),
            new Signal("CONT"),
//            new Signal("POLL"),
            new Signal("WINCH"),
//            new Signal("IO"),
            new Signal("PIPE"),
//            new Signal("HUP"),
//            new Signal("POLL"),
//            new Signal("PROF"),
//            new Signal("INT"),
//            new Signal("STKFLT"),
            new Signal("TSTP"),
//            new Signal("SYS"),
//            new Signal("TERM"),
//            new Signal("TRAP"),
//            new Signal("ABRT"),
            new Signal("TTOU"),
            new Signal("TTIN"),
//            new Signal("BUS"),
//            new Signal("VTALRM"),
//            new Signal("XCPU"),
//            new Signal("PWR")
    };

    private static final String[][] commands = new String[signals.length][];
    private static final long pid = ProcessHandle.current().pid();


    static {
        for (int i = 0; i < commands.length; i++) {
            commands[i] = new String[]{ "kill", "-" + signals[i].getName(), Long.toString(pid) };
        }
    }

    @Actor
    public void thread1() {
        for (String[] command : commands) {
            try {
                new ProcessBuilder(command)
                        .directory(null)
                        .start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Actor
    public void thread2(ZZZZZZZZ_Result r) {
        for (int i = 0; i < signals.length; i++) {
            Signal signal = signals[i];
            Signal.handle(signal, new MySignalHandler(i, r));
        }
    }

    private static class MySignalHandler implements SignalHandler {
        private final int num;
        private final ZZZZZZZZ_Result r;

        public MySignalHandler(int num, ZZZZZZZZ_Result r) {
            this.num = num;
            this.r = r;
        }

        @Override
        public void handle(Signal sig) {
            switch (num) {
                case 0:
                    r.r1 = true;
                    break;
                case 1:
                    r.r2 = true;
                    break;
                case 2:
                    r.r3 = true;
                    break;
                case 3:
                    r.r4 = true;
                case 4:
                    r.r5 = true;
                    break;
                case 5:
                    r.r6 = true;
                    break;
                case 6:
                    r.r7 = true;
                    break;
                case 7:
                    r.r8 = true;
                    break;
            }
        }
    }
}

Copy link
Member Author

Choose a reason for hiding this comment

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

I believe fixing this race is out of scope of current PR. Feel free to open a separate issue :)

oldHandler = handlers.put(sig, handler);
} else {
oldHandler = handlers.remove(sig);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

A ternary assignment might be an alternative here:

 Signal.Handler oldHandler = (newH == 2) ? handlers.put(sig, handler) : handlers.remove(sig);

Copy link
Member Author

Choose a reason for hiding this comment

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

Too much logic in the single line. I like if approach more.

@openjdk
Copy link

openjdk bot commented Jun 10, 2022

@turbanoff This change now passes all automated pre-integration checks.

ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details.

After integration, the commit message for the final commit will be:

8288140: Avoid redundant Hashtable.get call in Signal.handle

Reviewed-by: rriggs

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 25 new commits pushed to the master branch:

  • 7e940ef: 8286038: Update --release 19 symbol information for JDK 19 build 26
  • c6dd2ab: 8224768: Test ActalisCA.java fails
  • aaa8971: 8288227: Refactor annotation implementation to use pattern matching for instanceof
  • 2cc40af: 8287835: Add support for additional float/double to integral conversion for x86
  • 3ee1e60: 8288132: Update test artifacts in QuoVadis CA interop tests
  • 512db0f: 8271838: AmazonCA.java interop test fails
  • fcb35ed: 8287743: javax/swing/text/CSSBorder/6796710/bug6796710.java failed
  • bdd64d6: 8288181: AArch64: clean up out-of-date comments
  • 5d0e8b6: 8288203: runtime/ClassUnload/UnloadTestWithVerifyDuringGC.java fails with release VMs
  • 975316e: 8287902: UnreadableRB case in MissingResourceCauseTest is not working reliably on Windows
  • ... and 15 more: https://git.openjdk.org/jdk/compare/bc28baeba9360991e9b7575e1fbe178d873ccfc1...master

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

➡️ To integrate this PR with the above commit message to the master branch, type /integrate in a new comment.

@openjdk openjdk bot added the ready Pull request is ready to be integrated label Jun 10, 2022
@turbanoff
Copy link
Member Author

Thank you for review!
/integrate

@openjdk
Copy link

openjdk bot commented Jun 15, 2022

Going to push as commit dfeeb6f.
Since your change was applied there have been 69 commits pushed to the master branch:

  • 68b2057: 8287373: remove unnecessary paddings in generated code
  • 2471f8f: 8287647: VM debug support: find node by pattern in name or dump
  • 33f34d5: 8288207: Enhance MalformedURLException in Uri.parseCompat
  • 444a0d9: 8284977: MetricsTesterCgroupV2.getLongValueEntryFromFile fails when named value doesn't exist
  • 08400f1: 8287349: AArch64: Merge LDR instructions to improve C1 OSR performance
  • fe80721: 8287917: System.loadLibrary does not work on Big Sur if JDK is built with macOS SDK 10.15 and earlier
  • bbaeacb: 8265586: [windows] last button is not shown in AWT Frame with BorderLayout and MenuBar set.
  • 0f58097: 8288134: Super class names don't have envelopes
  • fb29770: 8287186: JDK modules participating in preview
  • 0530f4e: 8288094: cleanup old _MSC_VER handling
  • ... and 59 more: https://git.openjdk.org/jdk/compare/bc28baeba9360991e9b7575e1fbe178d873ccfc1...master

Your commit was automatically rebased without conflicts.

@openjdk openjdk bot added the integrated Pull request has been integrated label Jun 15, 2022
@openjdk openjdk bot closed this Jun 15, 2022
@openjdk openjdk bot removed ready Pull request is ready to be integrated rfr Pull request is ready for review labels Jun 15, 2022
@openjdk
Copy link

openjdk bot commented Jun 15, 2022

@turbanoff Pushed as commit dfeeb6f.

💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored.

@turbanoff turbanoff deleted the avoid_redundant_Hashtable.get_in_Signal branch October 27, 2022 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core-libs core-libs-dev@openjdk.org integrated Pull request has been integrated
5 participants