[Git][java-team/jnr-unixsocket][upstream] Imported Upstream version 0.38.21

Miguel Landaeta (@nomadium) gitlab at salsa.debian.org
Sat Dec 2 14:22:18 GMT 2023



Miguel Landaeta pushed to branch upstream at Debian Java Maintainers / jnr-unixsocket


Commits:
7390f438 by Miguel Landaeta at 2023-11-25T22:48:40+00:00
Imported Upstream version 0.38.21

- - - - -


24 changed files:

- + .github/workflows/ci.yml
- − .travis.yml
- LICENSE
- README.md
- pom.xml
- src/main/java/jnr/unixsocket/Common.java
- src/main/java/jnr/unixsocket/Native.java
- src/main/java/jnr/unixsocket/UnixDatagramChannel.java
- src/main/java/jnr/unixsocket/UnixServerSocketChannel.java
- src/main/java/jnr/unixsocket/UnixSocket.java
- src/main/java/jnr/unixsocket/UnixSocketChannel.java
- src/main/java/jnr/unixsocket/UnixSocketOptions.java
- src/main/java/jnr/enxio/channels/AbstractNativeDatagramChannel.java → src/main/java/jnr/unixsocket/impl/AbstractNativeDatagramChannel.java
- + src/main/java/jnr/unixsocket/impl/AbstractNativeServerSocketChannel.java
- src/main/java/jnr/enxio/channels/AbstractNativeSocketChannel.java → src/main/java/jnr/unixsocket/impl/AbstractNativeSocketChannel.java
- src/main/java/jnr/enxio/channels/Common.java → src/main/java/jnr/unixsocket/impl/Common.java
- src/test/java/jnr/unixsocket/BasicFunctionalityTest.java
- + src/test/java/jnr/unixsocket/SocketInteropTest.java
- + src/test/java/jnr/unixsocket/TcpChannelsApiSocketPair.java
- + src/test/java/jnr/unixsocket/TcpSocketsApiSocketPair.java
- + src/test/java/jnr/unixsocket/TestSocketPair.java
- src/test/java/jnr/unixsocket/UnixSocketChannelTest.java
- + src/test/java/jnr/unixsocket/UnixSocketPair.java
- src/test/java/jnr/unixsocket/example/UnixServer.java


Changes:

=====================================
.github/workflows/ci.yml
=====================================
@@ -0,0 +1,37 @@
+# This workflow will build a Java project with Maven
+# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
+
+name: Java CI with Maven
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+jobs:
+  jdk8:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout at v2
+    - name: Set up JDK 8
+      uses: actions/setup-java at v1.4.3
+      with:
+        java-version: 8
+    - name: Build with Maven
+      run: mvn -B package --file pom.xml
+
+  jdk11:
+
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout at v2
+    - name: Set up JDK 11
+      uses: actions/setup-java at v1.4.3
+      with:
+        java-version: 11
+    - name: Build with Maven
+      run: mvn -B package --file pom.xml


=====================================
.travis.yml deleted
=====================================
@@ -1,13 +0,0 @@
-language: java
-jdk:
-  - oraclejdk7
-  - openjdk7
-notifications:
-  irc:
-    channels:
-      - "irc.freenode.org#jnr"
-    on_success: change
-    on_failure: always
-    template:
-      - "%{repository} (%{branch}:%{commit} by %{author}): %{message} (%{build_url})"
-sudo: false


=====================================
LICENSE
=====================================
@@ -1,12 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
 
-  Licensed 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
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 
-      http://www.apache.org/licenses/LICENSE-2.0
+   1. Definitions.
 
-  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.
\ No newline at end of file
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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
+
+       http://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.


=====================================
README.md
=====================================
@@ -1,3 +1,8 @@
+[![][Build Status img]][Build Status]
+[![][license img]][license]
+[![][Maven Central img]][Maven Central]
+[![][Javadocs img]][Javadocs]
+
 jnr-unixsocket
 ==============
 
@@ -5,3 +10,14 @@ Native I/O access for java.
 
 Check out the [examples](https://github.com/jnr/jnr-unixsocket/tree/master/src/test/java/jnr/unixsocket/example) for more information.
 
+[Build Status]:https://travis-ci.org/jnr/jnr-unixsocket
+[Build Status img]:https://travis-ci.org/jnr/jnr-unixsocket.svg?branch=master
+
+[license]:LICENSE
+[license img]:https://img.shields.io/badge/license-Apache%202-blue.svg
+
+[Maven Central]:https://maven-badges.herokuapp.com/maven-central/com.github.jnr/jnr-unixsocket
+[Maven Central img]:https://maven-badges.herokuapp.com/maven-central/com.github.jnr/jnr-unixsocket/badge.svg
+
+[Javadocs]:http://javadoc.io/doc/com.github.jnr/jnr-unixsocket
+[Javadocs img]:http://javadoc.io/badge/com.github.jnr/jnr-unixsocket.svg


=====================================
pom.xml
=====================================
@@ -10,9 +10,9 @@
   <groupId>com.github.jnr</groupId>
   <artifactId>jnr-unixsocket</artifactId>
   <packaging>jar</packaging>
-  <version>0.18</version>
+  <version>0.38.21</version>
   <name>jnr-unixsocket</name>
-  <description>Native I/O access for java</description>
+  <description>UNIX socket channels for java</description>
   <url>http://github.com/jnr/jnr-unixsocket</url>
 
   <licenses>
@@ -43,35 +43,41 @@
     </developer>
   </developers>
 
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <maven.compiler.source>8</maven.compiler.source>
+    <maven.compiler.target>8</maven.compiler.target>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
-      <version>4.8.2</version>
+      <version>4.13.1</version>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>com.github.jnr</groupId>
       <artifactId>jnr-ffi</artifactId>
-      <version>2.1.4</version>
+      <version>2.2.15</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>com.github.jnr</groupId>
       <artifactId>jnr-constants</artifactId>
-      <version>0.9.8</version>
+      <version>0.10.4</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>com.github.jnr</groupId>
       <artifactId>jnr-enxio</artifactId>
-      <version>0.16</version>
+      <version>0.32.16</version>
       <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>com.github.jnr</groupId>
       <artifactId>jnr-posix</artifactId>
-      <version>3.0.35</version>
+      <version>3.1.18</version>
       <scope>compile</scope>
     </dependency>
   </dependencies>
@@ -109,12 +115,20 @@
         </executions>
       </plugin>
 
-      <!-- run findbugs check -->
+      <!-- run spotbugs check -->
       <plugin>
-        <groupId>org.codehaus.mojo</groupId>
-        <artifactId>findbugs-maven-plugin</artifactId>
-        <version>3.0.3</version>
-        <executions>
+        <groupId>com.github.spotbugs</groupId>
+        <artifactId>spotbugs-maven-plugin</artifactId>
+        <version>3.1.12.2</version>
+        <dependencies>
+          <!-- overwrite dependency on spotbugs if you want to specify the version of spotbugs -->
+          <dependency>
+            <groupId>com.github.spotbugs</groupId>
+            <artifactId>spotbugs</artifactId>
+            <version>4.0.0-beta4</version>
+          </dependency>
+        </dependencies>
+         <executions>
           <execution>
             <phase>process-test-classes</phase>
             <goals>
@@ -124,9 +138,9 @@
         </executions>
         <configuration>
           <effort>Max</effort>
-          <includeTests>true</includeTests>
-          <threshold>Low</threshold>
-          <xmlOutput>true</xmlOutput>
+          <includeTests>false</includeTests>
+          <relaxed>true</relaxed>
+          <spotbugsXmlOutput>true</spotbugsXmlOutput>
           <failOnError>false</failOnError>
         </configuration>
       </plugin>
@@ -134,7 +148,7 @@
       <!-- run PMD check -->
       <plugin>
         <artifactId>maven-pmd-plugin</artifactId>
-        <version>3.6</version>
+        <version>3.13.0</version>
         <configuration>
           <verbose>true</verbose>
           <linkXRef>false</linkXRef>
@@ -163,15 +177,6 @@
         </executions>
       </plugin>
 
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <version>2.0.2</version>
-        <configuration>
-          <source>1.7</source>
-          <target>1.7</target>
-        </configuration>
-      </plugin>
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
@@ -180,6 +185,7 @@
           <instructions>
             <_nouses>true</_nouses>
             <Import-Package>*,jnr.ffi.mapper,jnr.ffi.provider.converters,jnr.ffi.provider.jffi,com.kenai.jffi</Import-Package>
+            <Automatic-Module-Name>jnr.unixsocket</Automatic-Module-Name>
           </instructions>
         </configuration>
         <executions>
@@ -199,6 +205,9 @@
         <configuration>
           <archive>
             <manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
+            <manifestEntries>
+              <Automatic-Module-Name>org.jnrproject.unixsocket</Automatic-Module-Name>
+            </manifestEntries>
           </archive>
         </configuration>
         <executions>
@@ -223,19 +232,6 @@
           </execution>
         </executions>
       </plugin>
-      <plugin>
-        <groupId>org.apache.maven.plugins</groupId>
-        <artifactId>maven-javadoc-plugin</artifactId>
-        <version>2.10.3</version>
-        <executions>
-          <execution>
-            <id>attach-javadocs</id>
-            <goals>
-              <goal>jar</goal>
-            </goals>
-          </execution>
-        </executions>
-      </plugin>
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-assembly-plugin</artifactId>
@@ -255,6 +251,7 @@
           </execution>
         </executions>
       </plugin>
+<!--
       <plugin>
         <groupId>org.codehaus.mojo</groupId>
         <artifactId>exec-maven-plugin</artifactId>
@@ -296,6 +293,12 @@
           </execution>
         </executions>
       </plugin>
+-->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.8.1</version>
+      </plugin>
     </plugins>
     <extensions>
       <extension>
@@ -304,5 +307,17 @@
       </extension>
     </extensions>
   </build>
+
+  <profiles>
+    <profile>
+      <id>java9</id>
+      <activation>
+        <jdk>[9,)</jdk>
+      </activation>
+      <properties>
+        <maven.compiler.release>8</maven.compiler.release>
+      </properties>
+    </profile>
+  </profiles>
 </project>
 <!-- vim: set sw=2 ts=2 et: -->


=====================================
src/main/java/jnr/unixsocket/Common.java
=====================================
@@ -157,6 +157,8 @@ final class Common {
         wMap.put(UnixSocketOptions.SO_RCVTIMEO, jnr.constants.platform.SocketOption.SO_RCVTIMEO);
         wMap.put(UnixSocketOptions.SO_SNDTIMEO, jnr.constants.platform.SocketOption.SO_SNDTIMEO);
         wMap.put(UnixSocketOptions.SO_KEEPALIVE, jnr.constants.platform.SocketOption.SO_KEEPALIVE);
+        wMap.put(UnixSocketOptions.SO_PASSCRED, jnr.constants.platform.SocketOption.SO_PASSCRED);
+        
         rMap.putAll(wMap);
         rMap.put(UnixSocketOptions.SO_PEERCRED, jnr.constants.platform.SocketOption.SO_PEERCRED);
     }


=====================================
src/main/java/jnr/unixsocket/Native.java
=====================================
@@ -152,7 +152,7 @@ class Native {
             return libsocket().setsockopt(s, level.intValue(), optname.intValue(), t, DefaultNativeTimeval.size(t));
         } else {
             ByteBuffer buf = ByteBuffer.allocate(4);
-            buf.order(ByteOrder.BIG_ENDIAN);
+            buf.order(ByteOrder.nativeOrder());
             buf.putInt(optval).flip();
             return libsocket().setsockopt(s, level.intValue(), optname.intValue(), buf, buf.remaining());
         }
@@ -167,7 +167,7 @@ class Native {
             return (t.tv_sec.intValue() * 1000 + t.tv_usec.intValue() / 1000);
         } else {
             ByteBuffer buf = ByteBuffer.allocate(4);
-            buf.order(ByteOrder.BIG_ENDIAN);
+            buf.order(ByteOrder.nativeOrder());
             ref = new IntByReference(4);
             Native.libsocket().getsockopt(s, level.intValue(), optname, buf, ref);
             return buf.getInt();


=====================================
src/main/java/jnr/unixsocket/UnixDatagramChannel.java
=====================================
@@ -40,7 +40,7 @@ import java.util.Set;
 
 import jnr.constants.platform.ProtocolFamily;
 import jnr.constants.platform.Sock;
-import jnr.enxio.channels.AbstractNativeDatagramChannel;
+import jnr.unixsocket.impl.AbstractNativeDatagramChannel;
 
 public class UnixDatagramChannel extends AbstractNativeDatagramChannel {
     static enum State {
@@ -58,6 +58,10 @@ public class UnixDatagramChannel extends AbstractNativeDatagramChannel {
         return new UnixDatagramChannel();
     }
 
+    public static final UnixDatagramChannel open(ProtocolFamily domain, int protocol) throws IOException {
+        return new UnixDatagramChannel(domain, protocol);
+    }
+
     public static final UnixDatagramChannel[] pair() throws IOException {
         int[] sockets = { -1, -1 };
         Native.socketpair(ProtocolFamily.PF_UNIX, Sock.SOCK_DGRAM, 0, sockets);
@@ -71,6 +75,11 @@ public class UnixDatagramChannel extends AbstractNativeDatagramChannel {
         this(Native.socket(ProtocolFamily.PF_UNIX, Sock.SOCK_DGRAM, 0));
     }
 
+    UnixDatagramChannel(ProtocolFamily domain, int protocol) throws IOException
+    {
+        this(Native.socket(domain, Sock.SOCK_DGRAM, protocol));
+    }
+
     UnixDatagramChannel(int fd) {
         this(fd, State.IDLE, false);
     }
@@ -78,9 +87,12 @@ public class UnixDatagramChannel extends AbstractNativeDatagramChannel {
     UnixDatagramChannel(int fd, State initialState, boolean initialBoundState) {
         super(fd);
         stateLock.writeLock().lock();
-        state = initialState;
-        bindHandler = new BindHandler(initialBoundState);
-        stateLock.writeLock().unlock();
+        try {
+            state = initialState;
+            bindHandler = new BindHandler(initialBoundState);
+        } finally {
+            stateLock.writeLock().unlock();
+        }
     }
 
     UnixDatagramChannel(int fd, UnixSocketAddress remote) throws IOException {


=====================================
src/main/java/jnr/unixsocket/UnixServerSocketChannel.java
=====================================
@@ -20,17 +20,22 @@ package jnr.unixsocket;
 
 import jnr.constants.platform.ProtocolFamily;
 import jnr.constants.platform.Sock;
-import jnr.enxio.channels.NativeServerSocketChannel;
+import jnr.unixsocket.impl.AbstractNativeServerSocketChannel;
 import jnr.ffi.byref.IntByReference;
 
 import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NotYetBoundException;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.spi.SelectorProvider;
 
+import static jnr.unixsocket.Native.getLastError;
+import static jnr.unixsocket.Native.getLastErrorString;
+
 /**
  *
  */
-public class UnixServerSocketChannel extends NativeServerSocketChannel {
+public class UnixServerSocketChannel extends AbstractNativeServerSocketChannel {
 
     private final UnixServerSocket socket;
 
@@ -54,11 +59,24 @@ public class UnixServerSocketChannel extends NativeServerSocketChannel {
         int maxLength = addr.getMaximumLength();
         IntByReference len = new IntByReference(maxLength);
 
-        int clientfd = Native.accept(getFD(), addr, len);
+        int clientfd = -1;
+        begin();
+        try {
+            clientfd = Native.accept(getFD(), addr, len);
+        } finally {
+            end(clientfd >= 0);
+        }
 
         if (clientfd < 0) {
             if (isBlocking()) {
-                throw new IOException("accept failed: " + Native.getLastErrorString());
+                switch (getLastError()) {
+                    case EBADF:
+                        throw new ClosedChannelException();
+                    case EINVAL:
+                        throw new NotYetBoundException();
+                    default:
+                        throw new IOException("accept failed: " + getLastErrorString());
+                }
             }
 
             return null;


=====================================
src/main/java/jnr/unixsocket/UnixSocket.java
=====================================
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2009 Wayne Meissner
  * Copyright (C) 2016 Marcus Linke
- * 
+ *
  * (ported from https://github.com/softprops/unisockets/blob/master/unisockets-core/src/main/scala/Socket.scala)
  *
  * This file is part of the JNR project.
@@ -17,7 +17,7 @@
  * 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 jnr.unixsocket;
 
@@ -27,8 +27,12 @@ import java.io.OutputStream;
 import java.net.InetAddress;
 import java.net.SocketAddress;
 import java.net.SocketException;
+import java.nio.ByteBuffer;
 import java.nio.channels.Channels;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.SelectableChannel;
 import java.nio.channels.SocketChannel;
+import java.nio.channels.WritableByteChannel;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 public class UnixSocket extends java.net.Socket {
@@ -44,8 +48,8 @@ public class UnixSocket extends java.net.Socket {
 
     public UnixSocket(UnixSocketChannel chan) {
         this.chan = chan;
-        in = Channels.newInputStream(chan);
-        out = Channels.newOutputStream(chan);
+        in = Channels.newInputStream(new UnselectableByteChannel(chan));
+        out = Channels.newOutputStream(new UnselectableByteChannel(chan));
     }
 
     @Override
@@ -81,7 +85,8 @@ public class UnixSocket extends java.net.Socket {
         connect(addr, 0);
     }
 
-    public void connect(SocketAddress addr, Integer timeout) throws IOException {
+    @Override
+    public void connect(SocketAddress addr, int timeout) throws IOException {
         if (addr instanceof UnixSocketAddress) {
             chan.connect((UnixSocketAddress) addr);
         } else {
@@ -101,6 +106,7 @@ public class UnixSocket extends java.net.Socket {
         return null;
     }
 
+    @Override
     public InputStream getInputStream() throws IOException {
         if (chan.isConnected()) {
             return in;
@@ -111,12 +117,7 @@ public class UnixSocket extends java.net.Socket {
 
     @Override
     public SocketAddress getLocalSocketAddress() {
-        UnixSocketAddress address = chan.getLocalSocketAddress();
-        if (address != null) {
-            return address;
-        } else {
-            return null;
-        }
+        return chan.getLocalSocketAddress();
     }
 
     @Override
@@ -276,7 +277,43 @@ public class UnixSocket extends java.net.Socket {
             throw (SocketException)new SocketException().initCause(e);
         }
     }
-    
+
     private void ignore() {
     }
+
+    /**
+     * A byte channel that doesn't implement {@link SelectableChannel}. Though
+     * that type isn't in the public API, if the channel passed in implements
+     * that interface then unwanted synchronization is performed which can harm
+     * concurrency and can cause deadlocks.
+     *
+     * https://bugs.openjdk.java.net/browse/JDK-4774871
+     */
+    static final class UnselectableByteChannel implements ReadableByteChannel, WritableByteChannel {
+        private final UnixSocketChannel channel;
+
+        UnselectableByteChannel(UnixSocketChannel channel) {
+            this.channel = channel;
+        }
+
+        @Override
+        public int write(ByteBuffer src) throws IOException {
+            return channel.write(src);
+        }
+
+        @Override
+        public int read(ByteBuffer dst) throws IOException {
+            return channel.read(dst);
+        }
+
+        @Override
+        public boolean isOpen() {
+            return channel.isOpen();
+        }
+
+        @Override
+        public void close() throws IOException {
+            channel.close();
+        }
+    }
 }


=====================================
src/main/java/jnr/unixsocket/UnixSocketChannel.java
=====================================
@@ -35,7 +35,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
 import jnr.constants.platform.Errno;
 import jnr.constants.platform.ProtocolFamily;
 import jnr.constants.platform.Sock;
-import jnr.enxio.channels.AbstractNativeSocketChannel;
+import jnr.unixsocket.impl.AbstractNativeSocketChannel;
 import jnr.ffi.LastError;
 
 /**
@@ -80,7 +80,7 @@ public class UnixSocketChannel extends AbstractNativeSocketChannel {
     public static final UnixSocketChannel[] pair() throws IOException {
         int[] sockets = { -1, -1 };
         Native.socketpair(ProtocolFamily.PF_UNIX, Sock.SOCK_STREAM, 0, sockets);
-        return new UnixSocketChannel[] { 
+        return new UnixSocketChannel[] {
                 new UnixSocketChannel(sockets[0], State.CONNECTED, true),
                 new UnixSocketChannel(sockets[1], State.CONNECTED, true) };
     }
@@ -100,7 +100,7 @@ public class UnixSocketChannel extends AbstractNativeSocketChannel {
     UnixSocketChannel() throws IOException {
         this(Native.socket(ProtocolFamily.PF_UNIX, Sock.SOCK_STREAM, 0));
     }
-    
+
     UnixSocketChannel(int fd) {
         this(fd, State.CONNECTED, false);
     }
@@ -108,9 +108,12 @@ public class UnixSocketChannel extends AbstractNativeSocketChannel {
     UnixSocketChannel(int fd, State initialState, boolean initialBoundState) {
         super(fd);
         stateLock.writeLock().lock();
-        state = initialState;
-        bindHandler = new BindHandler(initialBoundState);
-        stateLock.writeLock().unlock();
+        try {
+            state = initialState;
+            bindHandler = new BindHandler(initialBoundState);
+        } finally {
+            stateLock.writeLock().unlock();
+        }
     }
 
     private boolean doConnect(SockAddrUnix remote) throws IOException {
@@ -146,7 +149,7 @@ public class UnixSocketChannel extends AbstractNativeSocketChannel {
             return true;
         }
     }
-    
+
     boolean isBound() {
         return bindHandler.isBound();
     }
@@ -287,6 +290,7 @@ public class UnixSocketChannel extends AbstractNativeSocketChannel {
             set.add(UnixSocketOptions.SO_RCVTIMEO);
             set.add(UnixSocketOptions.SO_PEERCRED);
             set.add(UnixSocketOptions.SO_KEEPALIVE);
+            set.add(UnixSocketOptions.SO_PASSCRED);
             return Collections.unmodifiableSet(set);
         }
     }


=====================================
src/main/java/jnr/unixsocket/UnixSocketOptions.java
=====================================
@@ -73,5 +73,11 @@ public final class UnixSocketOptions {
     public static final SocketOption<Credentials> SO_PEERCRED =
         new GenericOption<Credentials>("SO_PEERCRED", Credentials.class);
 
+    /**
+     * Enable credential transmission.
+     */
+    public static final SocketOption<Boolean> SO_PASSCRED =
+        new GenericOption<Boolean>("SO_PASSCRED", Boolean.class);
+
 }
 


=====================================
src/main/java/jnr/enxio/channels/AbstractNativeDatagramChannel.java → src/main/java/jnr/unixsocket/impl/AbstractNativeDatagramChannel.java
=====================================
@@ -16,7 +16,11 @@
  * limitations under the License.
  */
 
-package jnr.enxio.channels;
+package jnr.unixsocket.impl;
+
+import jnr.enxio.channels.Native;
+import jnr.enxio.channels.NativeSelectableChannel;
+import jnr.enxio.channels.NativeSelectorProvider;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;


=====================================
src/main/java/jnr/unixsocket/impl/AbstractNativeServerSocketChannel.java
=====================================
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 Jesse Wilson
+ *
+ * This file is part of the JNR project.
+ *
+ * Licensed 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
+ *
+ *    http://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 jnr.unixsocket.impl;
+
+import jnr.constants.platform.Shutdown;
+import jnr.enxio.channels.Native;
+import jnr.enxio.channels.NativeServerSocketChannel;
+
+import java.io.IOException;
+import java.nio.channels.spi.SelectorProvider;
+
+public abstract class AbstractNativeServerSocketChannel extends NativeServerSocketChannel {
+    public AbstractNativeServerSocketChannel(int fd) {
+        super(fd);
+    }
+
+    public AbstractNativeServerSocketChannel(SelectorProvider provider, int fd, int ops) {
+        super(provider, fd, ops);
+    }
+
+    @Override
+    protected void implCloseSelectableChannel() throws IOException {
+        // Shutdown to interrupt any potentially blocked threads. This is necessary on Linux.
+        Native.shutdown(getFD(), SHUT_RD);
+        Native.close(getFD());
+    }
+
+    private static final int SHUT_RD = Shutdown.SHUT_RD.intValue();
+}


=====================================
src/main/java/jnr/enxio/channels/AbstractNativeSocketChannel.java → src/main/java/jnr/unixsocket/impl/AbstractNativeSocketChannel.java
=====================================
@@ -16,7 +16,7 @@
  * limitations under the License.
  */
 
-package jnr.enxio.channels;
+package jnr.unixsocket.impl;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
@@ -24,7 +24,12 @@ import java.nio.channels.ByteChannel;
 import java.nio.channels.SocketChannel;
 import java.nio.channels.spi.SelectorProvider;
 
+import jnr.constants.platform.Errno;
 import jnr.constants.platform.Shutdown;
+import jnr.enxio.channels.Native;
+import jnr.enxio.channels.NativeException;
+import jnr.enxio.channels.NativeSelectableChannel;
+import jnr.enxio.channels.NativeSelectorProvider;
 
 public abstract class AbstractNativeSocketChannel extends SocketChannel
     implements ByteChannel, NativeSelectableChannel {
@@ -50,6 +55,11 @@ public abstract class AbstractNativeSocketChannel extends SocketChannel
 
     @Override
     protected void implCloseSelectableChannel() throws IOException {
+        if (this.isConnected()) {
+            this.shutdownInput();
+            this.shutdownOutput();
+        }
+
         Native.close(common.getFD());
     }
 
@@ -81,8 +91,8 @@ public abstract class AbstractNativeSocketChannel extends SocketChannel
     @Override
     public SocketChannel shutdownInput() throws IOException {
         int n = Native.shutdown(common.getFD(), SHUT_RD);
-        if (n < 0) {
-            throw new IOException(Native.getLastErrorString());
+        if (n < 0 && Native.getLastError() != Errno.ENOTCONN) {
+            throw new NativeException(Native.getLastErrorString(), Native.getLastError());
         }
         return this;
     }
@@ -90,8 +100,8 @@ public abstract class AbstractNativeSocketChannel extends SocketChannel
     @Override
     public SocketChannel shutdownOutput() throws IOException {
         int n = Native.shutdown(common.getFD(), SHUT_WR);
-        if (n < 0) {
-            throw new IOException(Native.getLastErrorString());
+        if (n < 0 && Native.getLastError() != Errno.ENOTCONN) {
+            throw new NativeException(Native.getLastErrorString(), Native.getLastError());
         }
         return this;
     }


=====================================
src/main/java/jnr/enxio/channels/Common.java → src/main/java/jnr/unixsocket/impl/Common.java
=====================================
@@ -16,12 +16,14 @@
  * limitations under the License.
  */
 
-package jnr.enxio.channels;
+package jnr.unixsocket.impl;
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
 
 import jnr.constants.platform.Errno;
+import jnr.enxio.channels.Native;
+import jnr.enxio.channels.NativeException;
 
 /**
  * Helper class, providing common methods.
@@ -64,7 +66,7 @@ final class Common {
                         return 0;
 
                     default:
-                        throw new IOException(Native.getLastErrorString());
+                        throw new NativeException(Native.getLastErrorString(), lastError);
                 }
 
             default: {
@@ -92,35 +94,54 @@ final class Common {
 
     int write(ByteBuffer src) throws IOException {
 
-        ByteBuffer buffer = ByteBuffer.allocate(src.remaining());
-
+        int r = src.remaining();
+        
+        ByteBuffer buffer = ByteBuffer.allocate(r);
+        
         buffer.put(src);
-
+        
         buffer.position(0);
 
         int n = Native.write(_fd, buffer);
 
-        if (n < 0) {
-            switch (Native.getLastError()) {
+        if (n >=0 ) {
+            if (n < r) {
+                src.position(src.position()-(r-n));
+            }
+        } else {
+            Errno lastError = Native.getLastError();
+            switch (lastError) {
                 case EAGAIN:
                 case EWOULDBLOCK:
+                    src.position(src.position()-r);
                     return 0;
             default:
-                throw new IOException(Native.getLastErrorString());
+                throw new NativeException(Native.getLastErrorString(), lastError);
             }
         }
 
         return n;
     }
 
-    long write(ByteBuffer[] srcs, int offset, int length)
-        throws IOException {
+    long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
 
         long result = 0;
-        int index = 0;
 
-        for (index = offset; index < length; index++) {
-            result += write(srcs[index]);
+        for (int index = offset; index < length; ++index) {
+            ByteBuffer buffer = srcs[index];
+            int remaining = buffer.remaining();
+            int written = 0;
+            while (true) {
+                int w = write(buffer);
+                written += w;
+                if (w == 0 || written == remaining) {
+                    break;
+                }
+            }
+            result += written;
+            if (written < remaining) {
+                break;
+            }
         }
 
         return result;


=====================================
src/test/java/jnr/unixsocket/BasicFunctionalityTest.java
=====================================
@@ -1,10 +1,11 @@
 
 package jnr.unixsocket;
 
-import static java.nio.charset.StandardCharsets.UTF_8;
-import static junit.framework.Assert.*;
+import jnr.enxio.channels.NativeSelectorProvider;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
-import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.net.SocketException;
@@ -16,19 +17,30 @@ import java.nio.channels.SelectionKey;
 import java.nio.channels.Selector;
 import java.util.Set;
 
-import jnr.enxio.channels.NativeSelectorProvider;
-
-import org.junit.Test;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 public class BasicFunctionalityTest {
-    private static final File SOCKADDR = new File("/tmp/jnr-unixsocket-test" + System.currentTimeMillis() + ".sock");
-    static { SOCKADDR.deleteOnExit(); }
-    private static final UnixSocketAddress ADDRESS = new UnixSocketAddress(SOCKADDR);
     private static final String DATA = "blah blah";
 
+    private UnixSocketPair socketPair;
     private Thread server;
     private volatile Exception serverException;
-    
+
+    @Before
+    public void setUp() throws Exception {
+        socketPair = new UnixSocketPair();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        socketPair.close();
+    }
+
     @Test
     public void doubleBindTest() throws Exception {
         UnixSocketChannel ch = UnixSocketChannel.open().bind(null);
@@ -44,7 +56,7 @@ public class BasicFunctionalityTest {
             }
         }
     }
-    
+
     @Test
     public void pairTest() throws Exception {
         UnixSocketChannel[] sp = UnixSocketChannel.pair();
@@ -61,7 +73,7 @@ public class BasicFunctionalityTest {
         final UnixServerSocketChannel channel = UnixServerSocketChannel.open();
         final Selector sel = NativeSelectorProvider.getInstance().openSelector();
         channel.configureBlocking(false);
-        channel.socket().bind(ADDRESS);
+        channel.socket().bind(socketPair.socketAddress());
         channel.register(sel, SelectionKey.OP_ACCEPT, new ServerActor(channel, sel));
 
         // TODO: This is ugly but simple enough. Many failures on server side will cause client to hang.
@@ -92,9 +104,9 @@ public class BasicFunctionalityTest {
         server.start();
 
         // client logic
-        UnixSocketChannel channel2 = UnixSocketChannel.open(ADDRESS);
+        UnixSocketChannel channel2 = UnixSocketChannel.open(socketPair.socketAddress());
 
-        assertEquals(ADDRESS, channel2.getRemoteSocketAddress());
+        assertEquals(socketPair.socketAddress(), channel2.getRemoteSocketAddress());
 
         Channels.newOutputStream(channel2).write(DATA.getBytes(UTF_8));
 
@@ -132,7 +144,7 @@ public class BasicFunctionalityTest {
                     // nonblocking result
                     return false;
                 }
-                assertEquals(ADDRESS, client.getLocalSocketAddress());
+                assertEquals(socketPair.socketAddress(), client.getLocalSocketAddress());
                 assertEquals("", client.getRemoteSocketAddress().getStruct().getPath());
 
                 client.configureBlocking(false);


=====================================
src/test/java/jnr/unixsocket/SocketInteropTest.java
=====================================
@@ -0,0 +1,212 @@
+package jnr.unixsocket;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.BufferedReader;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.SocketException;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.ClosedByInterruptException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.NotYetBoundException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Confirm that UNIX sockets work similarly to TCP sockets.
+ */
+ at RunWith(Parameterized.class)
+public class SocketInteropTest {
+    @Rule
+    public Timeout timeout = new Timeout(5, TimeUnit.SECONDS);
+
+    @Parameter
+    public TestSocketPair.Factory socketPairFactory;
+
+    private TestSocketPair socketPair;
+
+    @Parameters(name = "{0}")
+    public static List<Object[]> parameters() {
+        return Arrays.asList(
+                new Object[] { UnixSocketPair.FACTORY },
+                new Object[] { TcpSocketsApiSocketPair.FACTORY },
+                new Object[] { TcpChannelsApiSocketPair.FACTORY }
+        );
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        socketPair = socketPairFactory.createUnconnected();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        socketPair.close();
+    }
+
+    @Test
+    public void serverWritesAndClientReads() throws IOException {
+        socketPair.connectBlocking();
+
+        OutputStream serverOut = socketPair.server().getOutputStream();
+        serverOut.write("message from server to client\n".getBytes(UTF_8));
+        serverOut.flush();
+
+        InputStream clientIn = socketPair.client().getInputStream();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(clientIn, UTF_8));
+        assertEquals("message from server to client", reader.readLine());
+    }
+
+    @Test
+    public void clientWritesAndServerReads() throws IOException {
+        socketPair.connectBlocking();
+
+        OutputStream clientOut = socketPair.client().getOutputStream();
+        clientOut.write("message from client to server\n".getBytes(UTF_8));
+        clientOut.flush();
+
+        InputStream serverIn = socketPair.server().getInputStream();
+        BufferedReader reader = new BufferedReader(new InputStreamReader(serverIn, UTF_8));
+        assertEquals("message from client to server", reader.readLine());
+    }
+
+    @Test
+    public void acceptThrowsWhenServerSocketIsNotYetBound() throws IOException {
+        try {
+            socketPair.serverAccept();
+            fail();
+        } catch (NotYetBoundException expected) {
+            // Thrown by channels APIs.
+        } catch (SocketException expected) {
+            // Thrown by sockets APIs.
+        }
+    }
+
+    @Test
+    public void acceptThrowsWhenServerSocketIsClosed() throws IOException {
+        socketPair.serverBind();
+        socketPair.close();
+        try {
+            socketPair.serverAccept();
+            fail();
+        } catch (ClosedChannelException expected) {
+            // Thrown by channels APIs.
+        } catch (SocketException expected) {
+            // Thrown by sockets APIs.
+        }
+    }
+
+    @Test
+    public void acceptThrowsWhenServerSocketIsAsynchronouslyClosed() throws IOException {
+        socketPair.serverBind();
+        closeLater(socketPair, 500, TimeUnit.MILLISECONDS);
+        try {
+            socketPair.serverAccept();
+            fail();
+        } catch (AsynchronousCloseException expected) {
+            // Thrown by channels APIs.
+        } catch (SocketException expected) {
+            // Thrown by sockets APIs.
+        }
+    }
+
+    private void closeLater(final Closeable closeable, final long delay, final TimeUnit timeUnit) {
+        new Thread(getClass().getName() + ".closeLater") {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(timeUnit.toMillis(delay));
+                    closeable.close();
+                } catch (IOException | InterruptedException ignored) {
+                }
+            }
+        }.start();
+    }
+
+    @Test
+    public void acceptThrowsWhenAcceptingThreadIsInterrupted() throws IOException {
+        // https://bugs.openjdk.java.net/browse/JDK-4386498
+        assumeTrue("the TCP sockets API doesn't support Thread.interrupt()",
+                socketPairFactory != TcpSocketsApiSocketPair.FACTORY);
+
+        socketPair.serverBind();
+        interruptLater(Thread.currentThread(), 500, TimeUnit.MILLISECONDS);
+        try {
+            socketPair.serverAccept();
+            fail();
+        } catch (ClosedByInterruptException expected) {
+        }
+        // This has a side-effect of clearing the interrupted state. Otherwise later tests may fail!
+        assertTrue(Thread.interrupted());
+    }
+
+    private void interruptLater(final Thread target, final long delay, final TimeUnit timeUnit) {
+        new Thread(getClass().getName() + ".interruptLater") {
+            @Override
+            public void run() {
+                try {
+                    Thread.sleep(timeUnit.toMillis(delay));
+                    target.interrupt();
+                } catch (InterruptedException ignored) {
+                }
+            }
+        }.start();
+    }
+
+    @Test
+    public void concurrentReadAndWrite() throws IOException {
+        // https://bugs.openjdk.java.net/browse/JDK-4774871
+        assumeTrue("the TCP channels API doesn't support concurrent read and write",
+                socketPairFactory != TcpChannelsApiSocketPair.FACTORY);
+
+        socketPair.connectBlocking();
+
+        // This thread runs later. It writes messages on each socket.
+        new Thread(getClass().getName() + ".concurrentReadAndWrite") {
+            @Override
+            public void run() {
+                try {
+                    // Sleep to guarantee that the reads are in-flight before the writes are attempted.
+                    Thread.sleep(500);
+
+                    OutputStream clientOut = socketPair.client().getOutputStream();
+                    clientOut.write("message from client to server\n".getBytes(UTF_8));
+                    clientOut.flush();
+
+                    OutputStream serverOut = socketPair.server().getOutputStream();
+                    serverOut.write("message from server to client\n".getBytes(UTF_8));
+                    serverOut.flush();
+                } catch (InterruptedException | IOException ignored) {
+                }
+            }
+        }.start();
+
+        // This thread runs earlier. It reads messages on each socket.
+        InputStream clientIn = socketPair.client().getInputStream();
+        BufferedReader clientReader = new BufferedReader(new InputStreamReader(clientIn, UTF_8));
+        assertEquals("message from server to client", clientReader.readLine());
+
+        InputStream serverIn = socketPair.server().getInputStream();
+        BufferedReader serverReader = new BufferedReader(new InputStreamReader(serverIn, UTF_8));
+        assertEquals("message from client to server", serverReader.readLine());
+    }
+}


=====================================
src/test/java/jnr/unixsocket/TcpChannelsApiSocketPair.java
=====================================
@@ -0,0 +1,84 @@
+package jnr.unixsocket;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+
+/**
+ * TCP sockets created with the java.nio channels APIs.
+ */
+class TcpChannelsApiSocketPair extends TestSocketPair {
+    static final Factory FACTORY = new Factory() {
+        @Override
+        TestSocketPair createUnconnected() throws IOException {
+            return new TcpChannelsApiSocketPair();
+        }
+    };
+
+    private final ServerSocketChannel serverSocketChannel;
+    private InetSocketAddress serverAddress;
+    private SocketChannel serverChannel;
+    private SocketChannel clientChannel;
+
+    TcpChannelsApiSocketPair() throws IOException {
+        serverSocketChannel = ServerSocketChannel.open();
+    }
+
+    @Override
+    void serverBind() throws IOException {
+        if (serverAddress != null) {
+            throw new IllegalStateException("already bound");
+        }
+
+        ServerSocket serverSocket = serverSocketChannel.socket();
+        serverSocket.setReuseAddress(true);
+        serverSocketChannel.bind(new InetSocketAddress(0));
+        serverSocketChannel.configureBlocking(true);
+        serverAddress = new InetSocketAddress(serverSocket.getInetAddress(), serverSocket.getLocalPort());
+    }
+
+    @Override
+    void clientConnect() throws IOException {
+        if (clientChannel != null) {
+            throw new IllegalStateException("already connected");
+        }
+
+        clientChannel = SocketChannel.open();
+        clientChannel.connect(serverAddress);
+    }
+
+    @Override
+    void serverAccept() throws IOException {
+        if (serverChannel != null) {
+            throw new IllegalStateException("already accepted");
+        }
+
+        serverChannel = serverSocketChannel.accept();
+    }
+
+    @Override
+    SocketAddress socketAddress() {
+        return serverAddress;
+    }
+
+    @Override
+    Socket server() {
+        return serverChannel.socket();
+    }
+
+    @Override
+    Socket client() {
+        return clientChannel.socket();
+    }
+
+    @Override
+    public void close() throws IOException {
+        closeQuietly(serverSocketChannel);
+        closeQuietly(serverChannel);
+        closeQuietly(clientChannel);
+    }
+}


=====================================
src/test/java/jnr/unixsocket/TcpSocketsApiSocketPair.java
=====================================
@@ -0,0 +1,81 @@
+package jnr.unixsocket;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+
+/**
+ * TCP sockets created with the java.io sockets APIs. The sockets returned by
+ * this class do not have channels.
+ */
+class TcpSocketsApiSocketPair extends TestSocketPair {
+    static final Factory FACTORY = new Factory() {
+        @Override
+        TestSocketPair createUnconnected() throws IOException {
+            return new TcpSocketsApiSocketPair();
+        }
+    };
+
+    private final ServerSocket serverSocket;
+    private InetSocketAddress serverAddress;
+    private Socket server;
+    private Socket client;
+
+    public TcpSocketsApiSocketPair() throws IOException {
+        serverSocket = new ServerSocket();
+    }
+
+    @Override
+    void serverBind() throws IOException {
+        if (serverAddress != null) {
+            throw new IllegalStateException("already bound");
+        }
+
+        serverSocket.setReuseAddress(true);
+        serverSocket.bind(new InetSocketAddress(0));
+        serverAddress = new InetSocketAddress(serverSocket.getInetAddress(), serverSocket.getLocalPort());
+    }
+
+    @Override
+    void clientConnect() throws IOException {
+        if (client != null) {
+            throw new IllegalStateException("already connected");
+        }
+
+        client = new Socket();
+        client.connect(serverAddress);
+    }
+
+    @Override
+    void serverAccept() throws IOException {
+        if (server != null) {
+            throw new IllegalStateException("already accepted");
+        }
+
+        server = serverSocket.accept();
+    }
+
+    @Override
+    SocketAddress socketAddress() {
+        return serverAddress;
+    }
+
+    @Override
+    Socket server() {
+        return server;
+    }
+
+    @Override
+    Socket client() {
+        return client;
+    }
+
+    @Override
+    public void close() throws IOException {
+        closeQuietly(serverSocket);
+        closeQuietly(server);
+        closeQuietly(client);
+    }
+}


=====================================
src/test/java/jnr/unixsocket/TestSocketPair.java
=====================================
@@ -0,0 +1,43 @@
+package jnr.unixsocket;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.SocketAddress;
+
+/**
+ * A TCP or UNIX socket pair for testing.
+ */
+abstract class TestSocketPair implements Closeable {
+    void connectBlocking() throws IOException {
+        serverBind();
+        clientConnect();
+        serverAccept();
+    }
+
+    abstract void serverBind() throws IOException;
+
+    abstract void serverAccept() throws IOException;
+
+    abstract void clientConnect() throws IOException;
+
+    abstract SocketAddress socketAddress();
+
+    abstract Socket server();
+
+    abstract Socket client();
+
+    final void closeQuietly(Closeable closeable) {
+        if (closeable == null) {
+            return;
+        }
+        try {
+            closeable.close();
+        } catch (IOException ignored) {
+        }
+    }
+
+    abstract static class Factory {
+        abstract TestSocketPair createUnconnected() throws IOException;
+    }
+}


=====================================
src/test/java/jnr/unixsocket/UnixSocketChannelTest.java
=====================================
@@ -1,5 +1,10 @@
 package jnr.unixsocket;
 
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.CountDownLatch;
+
 import org.junit.Test;
 import org.junit.Assume;
 
@@ -48,4 +53,83 @@ public class UnixSocketChannelTest {
         assertEquals("local socket path", ABSTRACT, ch.getLocalSocketAddress().path());
     }
 
+    @Test
+    public void testInterruptRead() throws Exception {
+        Path socketPath = getTemporarySocketFileName();
+        startServer(socketPath);
+
+        int readTimeoutInMilliseconds = 5000;
+
+        UnixSocket socket = createClient(socketPath, readTimeoutInMilliseconds);
+        CountDownLatch readStartLatch = new CountDownLatch(1);
+        ReadFromSocketRunnable runnable = new ReadFromSocketRunnable(readStartLatch, socket);
+
+        Thread readThread = new Thread(runnable);
+
+        readThread.setDaemon(true);
+
+        long startTime = System.nanoTime();
+        readThread.start();
+        readStartLatch.await();
+        Thread.sleep(100); // Wait for the thread to call read()
+        socket.close();
+        readThread.join();
+        long stopTime = System.nanoTime();
+
+        long duration = stopTime - startTime;
+        long durationInMilliseconds = duration / 1_000_000;
+
+        assertTrue("read() was not interrupted by close() before read() timed out", durationInMilliseconds < readTimeoutInMilliseconds);
+        assertEquals("read() threw an exception", null, runnable.getThrownOnThread());
+    }
+
+    private Path getTemporarySocketFileName() throws IOException {
+        Path socketPath = Files.createTempFile("jnr-unixsocket-tests", ".sock");
+        Files.delete(socketPath);
+        socketPath.toFile().deleteOnExit();
+
+        return socketPath;
+    }
+
+    private void startServer(Path socketPath) throws IOException {
+        UnixServerSocketChannel serverChannel = UnixServerSocketChannel.open();
+        serverChannel.configureBlocking(false);
+        serverChannel.socket().bind(new UnixSocketAddress(socketPath.toFile()));
+    }
+
+    private UnixSocket createClient(Path socketPath, int readTimeoutInMilliseconds) throws IOException {
+        UnixSocketChannel clientChannel = UnixSocketChannel.open(new UnixSocketAddress(socketPath.toFile()));
+        UnixSocket socket = new UnixSocket(clientChannel);
+        socket.setSoTimeout(readTimeoutInMilliseconds);
+
+        return socket;
+    }
+
+    private class ReadFromSocketRunnable implements Runnable {
+        private CountDownLatch readStartLatch;
+        private UnixSocket socket;
+        private IOException thrownOnThread;
+
+        private ReadFromSocketRunnable(CountDownLatch readStartLatch, UnixSocket socket) {
+            this.readStartLatch = readStartLatch;
+            this.socket = socket;
+        }
+
+        @Override
+        public void run() {
+            try {
+                readStartLatch.countDown();
+                socket.getInputStream().read();
+            } catch (IOException e) {
+                // EBADF (bad file descriptor) is thrown when read() is interrupted
+                if (!e.getMessage().equals("Bad file descriptor")) {
+                    thrownOnThread = e;
+                }
+            }
+        }
+
+        private IOException getThrownOnThread() {
+            return thrownOnThread;
+        }
+    }
 }


=====================================
src/test/java/jnr/unixsocket/UnixSocketPair.java
=====================================
@@ -0,0 +1,76 @@
+package jnr.unixsocket;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.Socket;
+import java.util.UUID;
+
+class UnixSocketPair extends TestSocketPair {
+    static final Factory FACTORY = new Factory() {
+        @Override
+        TestSocketPair createUnconnected() throws IOException {
+            return new UnixSocketPair();
+        }
+    };
+
+    private final File file;
+    private final UnixSocketAddress address;
+
+    private UnixServerSocketChannel serverSocketChannel;
+    private UnixSocketChannel serverChannel;
+    private UnixSocketChannel clientChannel;
+
+    UnixSocketPair() throws IOException {
+        file = new File("/tmp/jnr-unixsocket-test" + UUID.randomUUID() + ".sock");
+        address = new UnixSocketAddress(file);
+        serverSocketChannel = UnixServerSocketChannel.open();
+    }
+
+    @Override
+    void serverBind() throws IOException {
+        serverSocketChannel.configureBlocking(true);
+        serverSocketChannel.socket().bind(address);
+    }
+
+    @Override
+    void clientConnect() throws IOException {
+        if (clientChannel != null) {
+            throw new IllegalStateException("already connected");
+        }
+
+        clientChannel = UnixSocketChannel.open();
+        clientChannel.connect(new UnixSocketAddress(file));
+    }
+
+    @Override
+    void serverAccept() throws IOException {
+        if (serverChannel != null) {
+            throw new IllegalStateException("already accepted");
+        }
+
+        serverChannel = serverSocketChannel.accept();
+    }
+
+    @Override
+    UnixSocketAddress socketAddress() {
+        return address;
+    }
+
+    @Override
+    Socket server() {
+        return serverChannel.socket();
+    }
+
+    @Override
+    Socket client() {
+        return clientChannel.socket();
+    }
+
+    @Override
+    public void close() throws IOException {
+        closeQuietly(serverSocketChannel);
+        closeQuietly(serverChannel);
+        closeQuietly(clientChannel);
+        file.delete();
+    }
+}


=====================================
src/test/java/jnr/unixsocket/example/UnixServer.java
=====================================
@@ -104,16 +104,19 @@ public class UnixServer {
         public final boolean rxready() {
             try {
                 ByteBuffer buf = ByteBuffer.allocate(1024);
-                int n = channel.read(buf);
-                UnixSocketAddress remote = channel.getRemoteSocketAddress();
-                System.out.printf("Read in %d bytes from %s\n", n, remote);
+                int n;
 
-                if (n > 0) {
-                    buf.flip();
-                    channel.write(buf);
-                    return true;
-                } else if (n < 0) {
-                    return false;
+                while ((n = channel.read(buf)) > 0) {
+                    UnixSocketAddress remote = channel.getRemoteSocketAddress();
+                    System.out.printf("Read in %d bytes from %s%n", n, remote);
+
+                    if (n > 0) {
+                        buf.flip();
+                        channel.write(buf);
+                        buf.clear();
+                    } else if (n < 0) {
+                        return false;
+                    }
                 }
 
             } catch (IOException ex) {



View it on GitLab: https://salsa.debian.org/java-team/jnr-unixsocket/-/commit/7390f438699e831235f79ab04b812d96f2f24eba

-- 
View it on GitLab: https://salsa.debian.org/java-team/jnr-unixsocket/-/commit/7390f438699e831235f79ab04b812d96f2f24eba
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20231202/d95504ec/attachment.htm>


More information about the pkg-java-commits mailing list