Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8345233

jdk.httpserver HttpExchange attributes are shared across requests

XMLWordPrintable

    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      I've tried on both Windows 10 and Mac OS Sonoma and I am able to see the issue.

      openjdk 23.0.1 2024-10-15
      OpenJDK Runtime Environment Corretto-23.0.1.8.1 (build 23.0.1+8-FR)
      OpenJDK 64-Bit Server VM Corretto-23.0.1.8.1 (build 23.0.1+8-FR, mixed mode, sharing)

      A DESCRIPTION OF THE PROBLEM :
      When creating a lightweight application with the jdk.httpserver and a virtual thread executor, HttpExchange instances appear to be shared across threads causing unexpected errors and making the service unusable. (attributes having different values than they should, errors sending responses, etc)

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      I have provided an example code snippet that showcases the issue.

      Run `java ExampleAPI.java` on the command line and view the logs.

      Alternatively one can use a debugger and set a breakpoint in the filter where the attribute is set, and see that HttpExchange contains the wrong values

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      the following responses in no particular order:

      jdk-http-0
      jdk-http-1
      jdk-http-2
      ACTUAL -
      Some permutation of:

      <correct-thread-name>
      attribute is <incorrect thread name> when should be: <correct-thread-name>
      attribute is <incorrect thread name> when should be: <correct-thread-name>

      ---------- BEGIN SOURCE ----------
      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.net.InetSocketAddress;
      import java.net.URI;
      import java.time.Duration;
      import java.time.temporal.ChronoUnit;
      import java.util.Random;
      import java.util.concurrent.Executors;
      import java.util.stream.Collectors;
      import com.sun.net.httpserver.Filter;
      import com.sun.net.httpserver.HttpExchange;
      import com.sun.net.httpserver.HttpServer;

      public class ExampleAPI {

        public static void main(String[] args) throws IOException {

          final Random random = new Random();
          var server = HttpServer.create(new InetSocketAddress(8080), 0);
          server.setExecutor(Executors.newThreadPerTaskExecutor(Thread.ofVirtual().name("jdk-http-", 0).factory()));
          server
              .createContext(
                  "/",
                  exchange -> {
                    var name = Thread.currentThread().getName();
                    try {

                      Thread.sleep(Duration.of(random.nextInt(1500, 5000), ChronoUnit.MILLIS));
                    } catch (InterruptedException e) {

                    }

                    var attribute = exchange.getAttribute("threadName");

                    if (!name.equals(attribute)) {
                      name = "attribute is " + attribute + " when should be: " + name;
                    }

                    var str = name.getBytes();
                    exchange.sendResponseHeaders(200, str.length);
                    exchange.getResponseBody().write(str);
                  })
              .getFilters()
              .add(new TestFilter());

          server.start();

          for (int i = 0; i < 3; i++) {

            Thread.startVirtualThread(
                () -> {
                  try (var is = URI.create("http://localhost:8080/travelpage").toURL().openStream(); ) {
                    System.err.println(
                        new BufferedReader(new InputStreamReader(is))
                            .lines()
                            .collect(Collectors.joining("\n")));
                  } catch (Exception e) {
                    e.printStackTrace();
                  }
                });
          }
        }

        static class TestFilter extends Filter {

          @Override
          public void doFilter(HttpExchange exchange, Chain chain) throws IOException {

            var name = Thread.currentThread().getName();
            exchange.setAttribute("threadName", name);

            try (exchange) {
              chain.doFilter(exchange);
            }

            if (exchange.getResponseCode() == -1) {

              exchange.sendResponseHeaders(500, -1);

            }
          }

          @Override
          public String description() {
            return "test";
          }
        }
      }
      ---------- END SOURCE ----------

      FREQUENCY : always


            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: