Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-75512

'sh' may hang for ever as some java implementation hide SIGPIPE by default

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: Major Major
    • durable-task-plugin
    • None

      Hi,

      When migrating our docker agent image from a RHEL 9 using JDK 22 or 23 to a RHEL 10 image using JDK 21 we have found a regression for some `sh` invocation that may hang forever. Basically, we have one shell script called by `sh` which eventually calls something like:

      ```

      RANDOM_STRING="$(tr -dc 'a-z0-9' < /dev/urandom | fold -w 4 | head -n1)"

      ```

      This works only if SIGPIPE isn't blocked, as we definitely expect the `head` command to immediately send a SIGPIPE to the `fold` command after one line as been echoed. If you run this in a "normal" shell, it works fine, however if you run this with SIGPIPE ignored, then you will see it hang for ever. On a recent distro, you can emulate this way:

      ```
      [amadeus@50ca067559e9]~% env --ignore-signal=SIGPIPE sh -c "tr -dc 'a-z0-9' < /dev/urandom | fold -w 4 | head -n1"

      aa78

      ^C <--- here it hangs for ever until hitting control + C

      ```

      It happens that some java implementation do hide the SIGPIPE signal by default. This is the case of the JDK 21 found on RHEL 9/10/Fedora. You can easily reproduce that in some docker images based on CentOS/Fedora and run this trivial java program that is just a wrapper for system commands passed as argument:

      ```
      [amadeus@50ca067559e9]~% cat plop.java
      import java.io.BufferedReader;   
      import java.io.InputStreamReader;
      import java.io.IOException;

      class TestSystemCommand

      {
          public static void main(String[] args)
          {
              try
              {
                  Process process = Runtime.getRuntime().exec(args);
                  BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
                  String line;

                  while ((line = reader.readLine()) != null)
                 

      {                 System.out.println(line);             }

                  reader.close();
              }
              catch (IOException e)
             

      {             e.printStackTrace();         }

          }
      }

      ```

      Which you can run like this:

      ```

      [amadeus@50ca067559e9]~% java plop.java sh -c "tr -dc 'a-z0-9' < /dev/urandom | fold -w 4 | head -n1"

      7ow9

      ^C% <--- Hang for ever until hitting control + C
      ```

      If you run a `quay.io/fedora/fedora` container and `dnf install -y java-latest-openjdk-headless java-21-openjdk-headless` then you can see that there is at least with openjdk some difference of behavior between JDK 21 and JDK 24:

      ```

      [root@3711488feefd /]# /usr/lib/jvm/java-21-openjdk/bin/java plop.java sh -c "tr -dc 'a-z0-9' < /dev/urandom | fold -w 4 | head -n1"
      5eco
      ^C[root@3711488feefd /]#  <--- Hangs for ever here
      [root@3711488feefd /]# /usr/lib/jvm/java-24-openjdk/bin/java plop.java sh -c "tr -dc 'a-z0-9' < /dev/urandom | fold -w 4 | head -n1"
      o8qn
      [root@3711488feefd /]#
      ```

      When using a recent coreutils (coreutils >= 8.31, released in 2019), this issue can be fixed by wrapping the command with the `env` command like this:

      ```
      [root@3711488feefd /]# /usr/lib/jvm/java-21-openjdk/bin/java plop.java env --default-signal=SIGPIPE sh -c "tr -dc 'a-z0-9' < /dev/urandom | fold -w 4 | head -n1"
      x0jw
      [root@3711488feefd /]# /usr/lib/jvm/java-24-openjdk/bin/java plop.java env --default-signal=SIGPIPE sh -c "tr -dc 'a-z0-9' < /dev/urandom | fold -w 4 | head -n1"
      s864
      ```

      In order to cope with both old and new OS, where `env` might know the  `--default-signal` argument, this more complex wrapper can be used:

      ```
      [root@3711488feefd /]# /usr/lib/jvm/java-21-openjdk/bin/java plop.java sh -c 'if env --default-signal=SIGPIPE true 1>/dev/null 2>&1; then exec env --default-signal=SIGPIPE "$@"; else exec "$@"; fi' – sh -c "tr -dc 'a-z0-9' < /dev/urandom | fold -w 4 | head -n1" 
      n3d7
      [root@3711488feefd /]# /usr/lib/jvm/java-24-openjdk/bin/java plop.java sh -c 'if env --default-signal=SIGPIPE true 1>/dev/null 2>&1; then exec env --default-signal=SIGPIPE "$@"; else exec "$@"; fi' – sh -c "tr -dc 'a-z0-9' < /dev/urandom | fold -w 4 | head -n1" 
      ormg
      ```

      I will submit a pull request in the Github repo to wrap the call to `nohup` with the above described workaround.

      Cheers,
      Romain

            Unassigned Unassigned
            romaingeissler1a Romain Geissler
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated: