ChannelFD.java

  1. package org.jruby.util.io;

  2. import jnr.enxio.channels.NativeDeviceChannel;
  3. import jnr.enxio.channels.NativeSelectableChannel;
  4. import jnr.posix.FileStat;
  5. import jnr.posix.POSIX;
  6. import org.jruby.Ruby;

  7. import java.io.Closeable;
  8. import java.io.IOException;
  9. import java.nio.channels.Channel;
  10. import java.nio.channels.ClosedChannelException;
  11. import java.nio.channels.FileChannel;
  12. import java.nio.channels.FileLock;
  13. import java.nio.channels.ReadableByteChannel;
  14. import java.nio.channels.SelectableChannel;
  15. import java.nio.channels.SocketChannel;
  16. import java.nio.channels.WritableByteChannel;
  17. import java.util.concurrent.atomic.AtomicInteger;

  18. /**
  19. * Created by headius on 5/24/14.
  20. */
  21. public class ChannelFD implements Closeable {
  22.     public ChannelFD(Channel fd, POSIX posix, FilenoUtil filenoUtil) {
  23.         assert fd != null;
  24.         this.ch = fd;
  25.         this.posix = posix;
  26.         this.filenoUtil = filenoUtil;

  27.         initFileno();
  28.         initChannelTypes();

  29.         refs = new AtomicInteger(1);

  30.         filenoUtil.registerWrapper(realFileno, this);
  31.         filenoUtil.registerWrapper(fakeFileno, this);
  32.     }

  33.     private void initFileno() {
  34.         realFileno = FilenoUtil.filenoFrom(ch);
  35.         if (realFileno == -1) {
  36.             fakeFileno = filenoUtil.getNewFileno();
  37.         } else {
  38.             fakeFileno = -1;
  39.         }
  40.     }

  41.     public ChannelFD dup() {
  42.         if (realFileno != -1) {
  43.             // real file descriptors, so we can dup directly
  44.             // TODO: investigate how badly this might damage JVM streams (prediction: not badly)
  45.             return new ChannelFD(new NativeDeviceChannel(posix.dup(realFileno)), posix, filenoUtil);
  46.         }

  47.         // TODO: not sure how well this combines native and non-native streams
  48.         // simulate dup by copying our channel into a new ChannelFD and incrementing ref count
  49.         Channel ch = this.ch;
  50.         ChannelFD fd = new ChannelFD(ch, posix, filenoUtil);
  51.         fd.refs = refs;
  52.         fd.refs.incrementAndGet();

  53.         return fd;
  54.     }

  55.     public int dup2From(POSIX posix, ChannelFD dup2Source) {
  56.         if (dup2Source.realFileno != -1 && realFileno != -1 && chFile == null) {
  57.             // real file descriptors, so we can dup2 directly
  58.             // ...but FileChannel tracks mode on its own, so we can't dup2 into it
  59.             // TODO: investigate how badly this might damage JVM streams (prediction: not badly)
  60.             return posix.dup2(dup2Source.realFileno, realFileno);
  61.         }

  62.         // TODO: not sure how well this combines native and non-native streams
  63.         // simulate dup2 by forcing filedes's channel into filedes2
  64.         this.ch = dup2Source.ch;
  65.         initFileno();
  66.         initChannelTypes();

  67.         this.refs = dup2Source.refs;
  68.         this.refs.incrementAndGet();

  69.         this.currentLock = dup2Source.currentLock;

  70.         return 0;
  71.     }

  72.     public void close() throws IOException {
  73.         // tidy up
  74.         finish(true);
  75.     }

  76.     public int bestFileno() {
  77.         return realFileno == -1 ? fakeFileno : realFileno;
  78.     }

  79.     private void finish(boolean close) throws IOException {
  80.         synchronized (refs) {
  81.             // if refcount is at or below zero, we're no longer valid
  82.             if (refs.get() <= 0) {
  83.                 throw new ClosedChannelException();
  84.             }

  85.             // if channel is already closed, we're no longer valid
  86.             if (!ch.isOpen()) {
  87.                 throw new ClosedChannelException();
  88.             }

  89.             // otherwise decrement and possibly close as normal
  90.             if (close) {
  91.                 int count = refs.decrementAndGet();

  92.                 if (count <= 0) {
  93.                     // if we're the last referrer, close the channel
  94.                     try {
  95.                         ch.close();
  96.                     } finally {
  97.                         filenoUtil.unregisterWrapper(realFileno);
  98.                         filenoUtil.unregisterWrapper(fakeFileno);
  99.                     }
  100.                 }
  101.             }
  102.         }
  103.     }

  104.     private void initChannelTypes() {
  105.         assert realFileno != -1 || fakeFileno != -1 : "initialize filenos before initChannelTypes";
  106.         if (ch instanceof ReadableByteChannel) chRead = (ReadableByteChannel)ch;
  107.         else chRead = null;
  108.         if (ch instanceof WritableByteChannel) chWrite = (WritableByteChannel)ch;
  109.         else chWrite = null;
  110.         if (ch instanceof FileChannel) chSeek = (FileChannel)ch;
  111.         else chSeek = null;
  112.         if (ch instanceof SelectableChannel) chSelect = (SelectableChannel)ch;
  113.         else chSelect = null;
  114.         if (ch instanceof FileChannel) chFile = (FileChannel)ch;
  115.         else chFile = null;
  116.         if (ch instanceof SocketChannel) chSock = (SocketChannel)ch;
  117.         else chSock = null;
  118.         if (ch instanceof NativeSelectableChannel) chNative = (NativeSelectableChannel)ch;
  119.         else chNative = null;

  120.         if (chNative != null) {
  121.             // we have an ENXIO channel, but need to know if it's a regular file to skip selection
  122.             FileStat stat = posix.fstat(chNative.getFD());
  123.             if (stat.isFile()) {
  124.                 chSelect = null;
  125.                 isNativeFile = true;
  126.             }
  127.         }
  128.     }

  129.     public Channel ch;
  130.     public ReadableByteChannel chRead;
  131.     public WritableByteChannel chWrite;
  132.     public FileChannel chSeek;
  133.     public SelectableChannel chSelect;
  134.     public FileChannel chFile;
  135.     public SocketChannel chSock;
  136.     public NativeSelectableChannel chNative;
  137.     public int realFileno;
  138.     public int fakeFileno;
  139.     private AtomicInteger refs;
  140.     public FileLock currentLock;
  141.     private POSIX posix;
  142.     public boolean isNativeFile = false;
  143.     private final FilenoUtil filenoUtil;
  144. }