Repository: netty/netty
Issue: Opt-out from blocking reads of OS files for classifiers

### Expected behavior

Less of redundant blocking IO in netty 

### Actual behavior

On initialization netty does [blocking](https://github.com/netty/netty/blob/c87c911c010c3a34a69e550d52299df20e5fc740/common/src/main/java/io/netty/util/internal/PlatformDependent.java#L231) reads of `/etc/os-release`, `/usr/lib/os-release` for
specific classifiers that are never available on some popular distros (e.g. ubuntu, alpine).
It would be helpful to avoid such blocking calls with system property - e.g. `-Dio.netty.readOsClassifiers=false`.

### Steps to reproduce

### Minimal yet complete reproducer code (or URL to code)

### Netty version

4.1.78.Final

### JVM version (e.g. `java -version`)

### OS version (e.g. `uname -a`)


Issue comments:
These are used by the native code loading. I think it'd be better to have a system property where the classifier set can be directly specified, and then avoid reading the files if that property is present. Then it could also serve a dual purpose of overriding the files in case they produce a wrong classifier set for some reason.

Pull request review comments:

Patch that resolved the issue:
diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent.java b/common/src/main/java/io/netty/util/internal/PlatformDependent.java
index 00310e585ea..cfa61f60406 100644
--- a/common/src/main/java/io/netty/util/internal/PlatformDependent.java
+++ b/common/src/main/java/io/netty/util/internal/PlatformDependent.java
@@ -219,6 +219,15 @@ public Random current() {
         final Set<String> allowedClassifiers = Collections.unmodifiableSet(
                 new HashSet<String>(Arrays.asList(ALLOWED_LINUX_OS_CLASSIFIERS)));
         final Set<String> availableClassifiers = new LinkedHashSet<String>();
+
+        if (!addPropertyOsClassifiers(allowedClassifiers, availableClassifiers)) {
+            addFilesystemOsClassifiers(allowedClassifiers, availableClassifiers);
+        }
+        LINUX_OS_CLASSIFIERS = Collections.unmodifiableSet(availableClassifiers);
+    }
+
+    static void addFilesystemOsClassifiers(final Set<String> allowedClassifiers,
+                                           final Set<String> availableClassifiers) {
         for (final String osReleaseFileName : OS_RELEASE_FILES) {
             final File file = new File(osReleaseFileName);
             boolean found = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@@ -271,7 +280,37 @@ public Boolean run() {
                 break;
             }
         }
-        LINUX_OS_CLASSIFIERS = Collections.unmodifiableSet(availableClassifiers);
+    }
+
+    static boolean addPropertyOsClassifiers(Set<String> allowedClassifiers, Set<String> availableClassifiers) {
+        // empty: -Dio.netty.osClassifiers (no distro specific classifiers for native libs)
+        // single ID: -Dio.netty.osClassifiers=ubuntu
+        // pair ID, ID_LIKE: -Dio.netty.osClassifiers=ubuntu,debian
+        // illegal otherwise
+        String osClassifiersPropertyName = "io.netty.osClassifiers";
+        String osClassifiers = SystemPropertyUtil.get(osClassifiersPropertyName);
+        if (osClassifiers == null) {
+            return false;
+        }
+        if (osClassifiers.isEmpty()) {
+            // let users omit classifiers with just -Dio.netty.osClassifiers
+            return true;
+        }
+        String[] classifiers = osClassifiers.split(",");
+        if (classifiers.length == 0) {
+            throw new IllegalArgumentException(
+                    osClassifiersPropertyName + " property is not empty, but contains no classifiers: "
+                            + osClassifiers);
+        }
+        // at most ID, ID_LIKE classifiers
+        if (classifiers.length > 2) {
+            throw new IllegalArgumentException(
+                    osClassifiersPropertyName + " property contains more than 2 classifiers: " + osClassifiers);
+        }
+        for (String classifier : classifiers) {
+            addClassifier(allowedClassifiers, availableClassifiers, classifier);
+        }
+        return true;
     }
 
     public static long byteArrayBaseOffset() {


Test patch:
diff --git a/common/src/test/java/io/netty/util/internal/OsClassifiersTest.java b/common/src/test/java/io/netty/util/internal/OsClassifiersTest.java
new file mode 100644
index 00000000000..903f5cba226
--- /dev/null
+++ b/common/src/test/java/io/netty/util/internal/OsClassifiersTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2022 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License, version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at:
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package io.netty.util.internal;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.function.Executable;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Properties;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class OsClassifiersTest {
+    private static final String OS_CLASSIFIERS_PROPERTY = "io.netty.osClassifiers";
+
+    private Properties systemProperties;
+
+    @BeforeEach
+    void setUp() {
+        systemProperties = System.getProperties();
+    }
+
+    @AfterEach
+    void tearDown() {
+        systemProperties.remove(OS_CLASSIFIERS_PROPERTY);
+    }
+
+    @Test
+    void testOsClassifiersPropertyAbsent() {
+        Set<String> allowed = new HashSet<String>(Arrays.asList("fedora", "suse", "arch"));
+        Set<String> available = new LinkedHashSet<String>(2);
+        boolean added = PlatformDependent.addPropertyOsClassifiers(allowed, available);
+        assertFalse(added);
+        assertTrue(available.isEmpty());
+    }
+
+    @Test
+    void testOsClassifiersPropertyEmpty() {
+        // empty property -Dio.netty.osClassifiers
+        systemProperties.setProperty(OS_CLASSIFIERS_PROPERTY, "");
+        Set<String> allowed = Collections.singleton("fedora");
+        Set<String> available = new LinkedHashSet<String>(2);
+        boolean added = PlatformDependent.addPropertyOsClassifiers(allowed, available);
+        assertTrue(added);
+        assertTrue(available.isEmpty());
+    }
+
+    @Test
+    void testOsClassifiersPropertyNotEmptyNoClassifiers() {
+        // ID
+        systemProperties.setProperty(OS_CLASSIFIERS_PROPERTY, ",");
+        final Set<String> allowed = new HashSet<String>(Arrays.asList("fedora", "suse", "arch"));
+        final Set<String> available = new LinkedHashSet<String>(2);
+        Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+            @Override
+            public void execute() throws Throwable {
+                PlatformDependent.addPropertyOsClassifiers(allowed, available);
+            }
+        });
+    }
+
+    @Test
+    void testOsClassifiersPropertySingle() {
+        // ID
+        systemProperties.setProperty(OS_CLASSIFIERS_PROPERTY, "fedora");
+        Set<String> allowed = Collections.singleton("fedora");
+        Set<String> available = new LinkedHashSet<String>(2);
+        boolean added = PlatformDependent.addPropertyOsClassifiers(allowed, available);
+        assertTrue(added);
+        assertEquals(1, available.size());
+        assertEquals("fedora", available.iterator().next());
+    }
+
+    @Test
+    void testOsClassifiersPropertyPair() {
+        // ID, ID_LIKE
+        systemProperties.setProperty(OS_CLASSIFIERS_PROPERTY, "manjaro,arch");
+        Set<String> allowed = new HashSet<String>(Arrays.asList("fedora", "suse", "arch"));
+        Set<String> available = new LinkedHashSet<String>(2);
+        boolean added = PlatformDependent.addPropertyOsClassifiers(allowed, available);
+        assertTrue(added);
+        assertEquals(1, available.size());
+        assertEquals("arch", available.iterator().next());
+    }
+
+    @Test
+    void testOsClassifiersPropertyExcessive() {
+        // ID, ID_LIKE, excessive
+        systemProperties.setProperty(OS_CLASSIFIERS_PROPERTY, "manjaro,arch,slackware");
+        final Set<String> allowed = new HashSet<String>(Arrays.asList("fedora", "suse", "arch"));
+        final Set<String> available = new LinkedHashSet<String>(2);
+        Assertions.assertThrows(IllegalArgumentException.class, new Executable() {
+            @Override
+            public void execute() throws Throwable {
+                PlatformDependent.addPropertyOsClassifiers(allowed, available);
+            }
+        });
+    }
+}