src/java.base/share/classes/sun/nio/ch/ServerSocketChannelImpl.java
changeset 54620 13b67c1420b8
parent 54154 1caf2daef7cf
child 54754 193a8f1a4f3b
child 57339 40fdbdd92617
equal deleted inserted replaced
54619:b43cc3b9ef40 54620:13b67c1420b8
    29 import java.io.IOException;
    29 import java.io.IOException;
    30 import java.net.InetSocketAddress;
    30 import java.net.InetSocketAddress;
    31 import java.net.ServerSocket;
    31 import java.net.ServerSocket;
    32 import java.net.SocketAddress;
    32 import java.net.SocketAddress;
    33 import java.net.SocketOption;
    33 import java.net.SocketOption;
       
    34 import java.net.SocketTimeoutException;
    34 import java.net.StandardSocketOptions;
    35 import java.net.StandardSocketOptions;
    35 import java.nio.channels.AlreadyBoundException;
    36 import java.nio.channels.AlreadyBoundException;
    36 import java.nio.channels.AsynchronousCloseException;
    37 import java.nio.channels.AsynchronousCloseException;
    37 import java.nio.channels.ClosedChannelException;
    38 import java.nio.channels.ClosedChannelException;
       
    39 import java.nio.channels.IllegalBlockingModeException;
    38 import java.nio.channels.NotYetBoundException;
    40 import java.nio.channels.NotYetBoundException;
    39 import java.nio.channels.SelectionKey;
    41 import java.nio.channels.SelectionKey;
    40 import java.nio.channels.ServerSocketChannel;
    42 import java.nio.channels.ServerSocketChannel;
    41 import java.nio.channels.SocketChannel;
    43 import java.nio.channels.SocketChannel;
    42 import java.nio.channels.spi.SelectorProvider;
    44 import java.nio.channels.spi.SelectorProvider;
    43 import java.util.Collections;
    45 import java.util.Collections;
    44 import java.util.HashSet;
    46 import java.util.HashSet;
    45 import java.util.Objects;
    47 import java.util.Objects;
    46 import java.util.Set;
    48 import java.util.Set;
       
    49 import java.util.concurrent.locks.Condition;
    47 import java.util.concurrent.locks.ReentrantLock;
    50 import java.util.concurrent.locks.ReentrantLock;
    48 
    51 
    49 import sun.net.NetHooks;
    52 import sun.net.NetHooks;
    50 import sun.net.ext.ExtendedSocketOptions;
    53 import sun.net.ext.ExtendedSocketOptions;
    51 
    54 
    67     // Lock held by thread currently blocked on this channel
    70     // Lock held by thread currently blocked on this channel
    68     private final ReentrantLock acceptLock = new ReentrantLock();
    71     private final ReentrantLock acceptLock = new ReentrantLock();
    69 
    72 
    70     // Lock held by any thread that modifies the state fields declared below
    73     // Lock held by any thread that modifies the state fields declared below
    71     // DO NOT invoke a blocking I/O operation while holding this lock!
    74     // DO NOT invoke a blocking I/O operation while holding this lock!
    72     private final Object stateLock = new Object();
    75     private final ReentrantLock stateLock = new ReentrantLock();
       
    76     private final Condition stateCondition = stateLock.newCondition();
    73 
    77 
    74     // -- The following fields are protected by stateLock
    78     // -- The following fields are protected by stateLock
    75 
    79 
    76     // Channel state, increases monotonically
    80     // Channel state, increases monotonically
    77     private static final int ST_INUSE = 0;
    81     private static final int ST_INUSE = 0;
    93     private ServerSocket socket;
    97     private ServerSocket socket;
    94 
    98 
    95     // -- End of fields protected by stateLock
    99     // -- End of fields protected by stateLock
    96 
   100 
    97 
   101 
    98     ServerSocketChannelImpl(SelectorProvider sp) throws IOException {
   102     ServerSocketChannelImpl(SelectorProvider sp) {
    99         super(sp);
   103         super(sp);
   100         this.fd = Net.serverSocket(true);
   104         this.fd = Net.serverSocket(true);
   101         this.fdVal = IOUtil.fdVal(fd);
   105         this.fdVal = IOUtil.fdVal(fd);
   102     }
   106     }
   103 
   107 
   106     {
   110     {
   107         super(sp);
   111         super(sp);
   108         this.fd =  fd;
   112         this.fd =  fd;
   109         this.fdVal = IOUtil.fdVal(fd);
   113         this.fdVal = IOUtil.fdVal(fd);
   110         if (bound) {
   114         if (bound) {
   111             synchronized (stateLock) {
   115             stateLock.lock();
       
   116             try {
   112                 localAddress = Net.localAddress(fd);
   117                 localAddress = Net.localAddress(fd);
       
   118             } finally {
       
   119                 stateLock.unlock();
   113             }
   120             }
   114         }
   121         }
   115     }
   122     }
   116 
   123 
   117     // @throws ClosedChannelException if channel is closed
   124     // @throws ClosedChannelException if channel is closed
   120             throw new ClosedChannelException();
   127             throw new ClosedChannelException();
   121     }
   128     }
   122 
   129 
   123     @Override
   130     @Override
   124     public ServerSocket socket() {
   131     public ServerSocket socket() {
   125         synchronized (stateLock) {
   132         stateLock.lock();
       
   133         try {
   126             if (socket == null)
   134             if (socket == null)
   127                 socket = ServerSocketAdaptor.create(this);
   135                 socket = ServerSocketAdaptor.create(this);
   128             return socket;
   136             return socket;
       
   137         } finally {
       
   138             stateLock.unlock();
   129         }
   139         }
   130     }
   140     }
   131 
   141 
   132     @Override
   142     @Override
   133     public SocketAddress getLocalAddress() throws IOException {
   143     public SocketAddress getLocalAddress() throws IOException {
   134         synchronized (stateLock) {
   144         stateLock.lock();
       
   145         try {
   135             ensureOpen();
   146             ensureOpen();
   136             return (localAddress == null)
   147             return (localAddress == null)
   137                     ? null
   148                     ? null
   138                     : Net.getRevealedLocalAddress(localAddress);
   149                     : Net.getRevealedLocalAddress(localAddress);
       
   150         } finally {
       
   151             stateLock.unlock();
   139         }
   152         }
   140     }
   153     }
   141 
   154 
   142     @Override
   155     @Override
   143     public <T> ServerSocketChannel setOption(SocketOption<T> name, T value)
   156     public <T> ServerSocketChannel setOption(SocketOption<T> name, T value)
   144         throws IOException
   157         throws IOException
   145     {
   158     {
   146         Objects.requireNonNull(name);
   159         Objects.requireNonNull(name);
   147         if (!supportedOptions().contains(name))
   160         if (!supportedOptions().contains(name))
   148             throw new UnsupportedOperationException("'" + name + "' not supported");
   161             throw new UnsupportedOperationException("'" + name + "' not supported");
   149         synchronized (stateLock) {
   162         stateLock.lock();
       
   163         try {
   150             ensureOpen();
   164             ensureOpen();
   151 
   165 
   152             if (name == StandardSocketOptions.SO_REUSEADDR && Net.useExclusiveBind()) {
   166             if (name == StandardSocketOptions.SO_REUSEADDR && Net.useExclusiveBind()) {
   153                 // SO_REUSEADDR emulated when using exclusive bind
   167                 // SO_REUSEADDR emulated when using exclusive bind
   154                 isReuseAddress = (Boolean)value;
   168                 isReuseAddress = (Boolean)value;
   155             } else {
   169             } else {
   156                 // no options that require special handling
   170                 // no options that require special handling
   157                 Net.setSocketOption(fd, Net.UNSPEC, name, value);
   171                 Net.setSocketOption(fd, Net.UNSPEC, name, value);
   158             }
   172             }
   159             return this;
   173             return this;
       
   174         } finally {
       
   175             stateLock.unlock();
   160         }
   176         }
   161     }
   177     }
   162 
   178 
   163     @Override
   179     @Override
   164     @SuppressWarnings("unchecked")
   180     @SuppressWarnings("unchecked")
   167     {
   183     {
   168         Objects.requireNonNull(name);
   184         Objects.requireNonNull(name);
   169         if (!supportedOptions().contains(name))
   185         if (!supportedOptions().contains(name))
   170             throw new UnsupportedOperationException("'" + name + "' not supported");
   186             throw new UnsupportedOperationException("'" + name + "' not supported");
   171 
   187 
   172         synchronized (stateLock) {
   188         stateLock.lock();
       
   189         try {
   173             ensureOpen();
   190             ensureOpen();
   174             if (name == StandardSocketOptions.SO_REUSEADDR && Net.useExclusiveBind()) {
   191             if (name == StandardSocketOptions.SO_REUSEADDR && Net.useExclusiveBind()) {
   175                 // SO_REUSEADDR emulated when using exclusive bind
   192                 // SO_REUSEADDR emulated when using exclusive bind
   176                 return (T)Boolean.valueOf(isReuseAddress);
   193                 return (T)Boolean.valueOf(isReuseAddress);
   177             }
   194             }
   178             // no options that require special handling
   195             // no options that require special handling
   179             return (T) Net.getSocketOption(fd, Net.UNSPEC, name);
   196             return (T) Net.getSocketOption(fd, Net.UNSPEC, name);
       
   197         } finally {
       
   198             stateLock.unlock();
   180         }
   199         }
   181     }
   200     }
   182 
   201 
   183     private static class DefaultOptionsHolder {
   202     private static class DefaultOptionsHolder {
   184         static final Set<SocketOption<?>> defaultOptions = defaultOptions();
   203         static final Set<SocketOption<?>> defaultOptions = defaultOptions();
   200         return DefaultOptionsHolder.defaultOptions;
   219         return DefaultOptionsHolder.defaultOptions;
   201     }
   220     }
   202 
   221 
   203     @Override
   222     @Override
   204     public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException {
   223     public ServerSocketChannel bind(SocketAddress local, int backlog) throws IOException {
   205         synchronized (stateLock) {
   224         stateLock.lock();
       
   225         try {
   206             ensureOpen();
   226             ensureOpen();
   207             if (localAddress != null)
   227             if (localAddress != null)
   208                 throw new AlreadyBoundException();
   228                 throw new AlreadyBoundException();
   209             InetSocketAddress isa = (local == null)
   229             InetSocketAddress isa = (local == null)
   210                                     ? new InetSocketAddress(0)
   230                                     ? new InetSocketAddress(0)
   214                 sm.checkListen(isa.getPort());
   234                 sm.checkListen(isa.getPort());
   215             NetHooks.beforeTcpBind(fd, isa.getAddress(), isa.getPort());
   235             NetHooks.beforeTcpBind(fd, isa.getAddress(), isa.getPort());
   216             Net.bind(fd, isa.getAddress(), isa.getPort());
   236             Net.bind(fd, isa.getAddress(), isa.getPort());
   217             Net.listen(fd, backlog < 1 ? 50 : backlog);
   237             Net.listen(fd, backlog < 1 ? 50 : backlog);
   218             localAddress = Net.localAddress(fd);
   238             localAddress = Net.localAddress(fd);
       
   239         } finally {
       
   240             stateLock.unlock();
   219         }
   241         }
   220         return this;
   242         return this;
   221     }
   243     }
   222 
   244 
   223     /**
   245     /**
   227      * @throws NotYetBoundException if the channel's socket has not been bound yet
   249      * @throws NotYetBoundException if the channel's socket has not been bound yet
   228      */
   250      */
   229     private void begin(boolean blocking) throws ClosedChannelException {
   251     private void begin(boolean blocking) throws ClosedChannelException {
   230         if (blocking)
   252         if (blocking)
   231             begin();  // set blocker to close channel if interrupted
   253             begin();  // set blocker to close channel if interrupted
   232         synchronized (stateLock) {
   254         stateLock.lock();
       
   255         try {
   233             ensureOpen();
   256             ensureOpen();
   234             if (localAddress == null)
   257             if (localAddress == null)
   235                 throw new NotYetBoundException();
   258                 throw new NotYetBoundException();
   236             if (blocking)
   259             if (blocking)
   237                 thread = NativeThread.current();
   260                 thread = NativeThread.current();
       
   261         } finally {
       
   262             stateLock.unlock();
   238         }
   263         }
   239     }
   264     }
   240 
   265 
   241     /**
   266     /**
   242      * Marks the end of an I/O operation that may have blocked.
   267      * Marks the end of an I/O operation that may have blocked.
   246      */
   271      */
   247     private void end(boolean blocking, boolean completed)
   272     private void end(boolean blocking, boolean completed)
   248         throws AsynchronousCloseException
   273         throws AsynchronousCloseException
   249     {
   274     {
   250         if (blocking) {
   275         if (blocking) {
   251             synchronized (stateLock) {
   276             stateLock.lock();
       
   277             try {
   252                 thread = 0;
   278                 thread = 0;
   253                 // notify any thread waiting in implCloseSelectableChannel
   279                 // notify any thread waiting in implCloseSelectableChannel
   254                 if (state == ST_CLOSING) {
   280                 if (state == ST_CLOSING) {
   255                     stateLock.notifyAll();
   281                     stateCondition.signalAll();
   256                 }
   282                 }
       
   283             } finally {
       
   284                 stateLock.unlock();
   257             }
   285             }
   258             end(completed);
   286             end(completed);
   259         }
   287         }
   260     }
   288     }
   261 
   289 
   268         acceptLock.lock();
   296         acceptLock.lock();
   269         try {
   297         try {
   270             boolean blocking = isBlocking();
   298             boolean blocking = isBlocking();
   271             try {
   299             try {
   272                 begin(blocking);
   300                 begin(blocking);
   273                 do {
   301                 n = Net.accept(this.fd, newfd, isaa);
   274                     n = Net.accept(this.fd, newfd, isaa);
   302                 if (blocking) {
   275                 } while (n == IOStatus.INTERRUPTED && isOpen());
   303                     while (IOStatus.okayToRetry(n) && isOpen()) {
       
   304                         park(Net.POLLIN);
       
   305                         n = Net.accept(this.fd, newfd, isaa);
       
   306                     }
       
   307                 }
   276             } finally {
   308             } finally {
   277                 end(blocking, n > 0);
   309                 end(blocking, n > 0);
   278                 assert IOStatus.check(n);
   310                 assert IOStatus.check(n);
   279             }
   311             }
   280 
       
   281         } finally {
   312         } finally {
   282             acceptLock.unlock();
   313             acceptLock.unlock();
   283         }
   314         }
   284 
   315 
   285         if (n < 1)
   316         if (n > 0) {
       
   317             return finishAccept(newfd, isaa[0]);
       
   318         } else {
   286             return null;
   319             return null;
   287 
   320         }
   288         InetSocketAddress isa = isaa[0];
   321     }
       
   322 
       
   323     /**
       
   324      * Accepts a new connection with a given timeout. This method requires the
       
   325      * channel to be configured in blocking mode.
       
   326      *
       
   327      * @apiNote This method is for use by the socket adaptor.
       
   328      *
       
   329      * @param nanos the timeout, in nanoseconds
       
   330      * @throws IllegalBlockingModeException if the channel is configured non-blocking
       
   331      * @throws SocketTimeoutException if the timeout expires
       
   332      */
       
   333     SocketChannel blockingAccept(long nanos) throws IOException {
       
   334         int n = 0;
       
   335         FileDescriptor newfd = new FileDescriptor();
       
   336         InetSocketAddress[] isaa = new InetSocketAddress[1];
       
   337 
       
   338         acceptLock.lock();
       
   339         try {
       
   340             // check that channel is configured blocking
       
   341             if (!isBlocking())
       
   342                 throw new IllegalBlockingModeException();
       
   343 
       
   344             try {
       
   345                 begin(true);
       
   346                 // change socket to non-blocking
       
   347                 lockedConfigureBlocking(false);
       
   348                 try {
       
   349                     long startNanos = System.nanoTime();
       
   350                     n = Net.accept(fd, newfd, isaa);
       
   351                     while (n == IOStatus.UNAVAILABLE && isOpen()) {
       
   352                         long remainingNanos = nanos - (System.nanoTime() - startNanos);
       
   353                         if (remainingNanos <= 0) {
       
   354                             throw new SocketTimeoutException("Accept timed out");
       
   355                         }
       
   356                         park(Net.POLLIN, remainingNanos);
       
   357                         n = Net.accept(fd, newfd, isaa);
       
   358                     }
       
   359                 } finally {
       
   360                     // restore socket to blocking mode
       
   361                     lockedConfigureBlocking(true);
       
   362                 }
       
   363             } finally {
       
   364                 end(true, n > 0);
       
   365             }
       
   366         } finally {
       
   367             acceptLock.unlock();
       
   368         }
       
   369 
       
   370         assert n > 0;
       
   371         return finishAccept(newfd, isaa[0]);
       
   372     }
       
   373 
       
   374     private SocketChannel finishAccept(FileDescriptor newfd, InetSocketAddress isa)
       
   375         throws IOException
       
   376     {
   289         try {
   377         try {
   290             // newly accepted socket is initially in blocking mode
   378             // newly accepted socket is initially in blocking mode
   291             IOUtil.configureBlocking(newfd, true);
   379             IOUtil.configureBlocking(newfd, true);
   292 
   380 
   293             // check permitted to accept connections from the remote address
   381             // check permitted to accept connections from the remote address
   304 
   392 
   305     @Override
   393     @Override
   306     protected void implConfigureBlocking(boolean block) throws IOException {
   394     protected void implConfigureBlocking(boolean block) throws IOException {
   307         acceptLock.lock();
   395         acceptLock.lock();
   308         try {
   396         try {
   309             synchronized (stateLock) {
   397             lockedConfigureBlocking(block);
   310                 ensureOpen();
       
   311                 IOUtil.configureBlocking(fd, block);
       
   312             }
       
   313         } finally {
   398         } finally {
   314             acceptLock.unlock();
   399             acceptLock.unlock();
       
   400         }
       
   401     }
       
   402 
       
   403     /**
       
   404      * Adjust the blocking mode while holding acceptLock.
       
   405      */
       
   406     private void lockedConfigureBlocking(boolean block) throws IOException {
       
   407         assert acceptLock.isHeldByCurrentThread();
       
   408         stateLock.lock();
       
   409         try {
       
   410             ensureOpen();
       
   411             IOUtil.configureBlocking(fd, block);
       
   412         } finally {
       
   413             stateLock.unlock();
   315         }
   414         }
   316     }
   415     }
   317 
   416 
   318     /**
   417     /**
   319      * Invoked by implCloseChannel to close the channel.
   418      * Invoked by implCloseChannel to close the channel.
   334 
   433 
   335         boolean interrupted = false;
   434         boolean interrupted = false;
   336         boolean blocking;
   435         boolean blocking;
   337 
   436 
   338         // set state to ST_CLOSING
   437         // set state to ST_CLOSING
   339         synchronized (stateLock) {
   438         stateLock.lock();
       
   439         try {
   340             assert state < ST_CLOSING;
   440             assert state < ST_CLOSING;
   341             state = ST_CLOSING;
   441             state = ST_CLOSING;
   342             blocking = isBlocking();
   442             blocking = isBlocking();
       
   443         } finally {
       
   444             stateLock.unlock();
   343         }
   445         }
   344 
   446 
   345         // wait for any outstanding accept to complete
   447         // wait for any outstanding accept to complete
   346         if (blocking) {
   448         if (blocking) {
   347             synchronized (stateLock) {
   449             stateLock.lock();
       
   450             try {
   348                 assert state == ST_CLOSING;
   451                 assert state == ST_CLOSING;
   349                 long th = thread;
   452                 long th = thread;
   350                 if (th != 0) {
   453                 if (th != 0) {
   351                     nd.preClose(fd);
   454                     nd.preClose(fd);
   352                     NativeThread.signal(th);
   455                     NativeThread.signal(th);
   353 
   456 
   354                     // wait for accept operation to end
   457                     // wait for accept operation to end
   355                     while (thread != 0) {
   458                     while (thread != 0) {
   356                         try {
   459                         try {
   357                             stateLock.wait();
   460                             stateCondition.await();
   358                         } catch (InterruptedException e) {
   461                         } catch (InterruptedException e) {
   359                             interrupted = true;
   462                             interrupted = true;
   360                         }
   463                         }
   361                     }
   464                     }
   362                 }
   465                 }
       
   466             } finally {
       
   467                 stateLock.unlock();
   363             }
   468             }
   364         } else {
   469         } else {
   365             // non-blocking mode: wait for accept to complete
   470             // non-blocking mode: wait for accept to complete
   366             acceptLock.lock();
   471             acceptLock.lock();
   367             acceptLock.unlock();
   472             acceptLock.unlock();
   368         }
   473         }
   369 
   474 
   370         // set state to ST_KILLPENDING
   475         // set state to ST_KILLPENDING
   371         synchronized (stateLock) {
   476         stateLock.lock();
       
   477         try {
   372             assert state == ST_CLOSING;
   478             assert state == ST_CLOSING;
   373             state = ST_KILLPENDING;
   479             state = ST_KILLPENDING;
       
   480         } finally {
       
   481             stateLock.unlock();
   374         }
   482         }
   375 
   483 
   376         // close socket if not registered with Selector
   484         // close socket if not registered with Selector
   377         if (!isRegistered())
   485         if (!isRegistered())
   378             kill();
   486             kill();
   382             Thread.currentThread().interrupt();
   490             Thread.currentThread().interrupt();
   383     }
   491     }
   384 
   492 
   385     @Override
   493     @Override
   386     public void kill() throws IOException {
   494     public void kill() throws IOException {
   387         synchronized (stateLock) {
   495         stateLock.lock();
       
   496         try {
   388             if (state == ST_KILLPENDING) {
   497             if (state == ST_KILLPENDING) {
   389                 state = ST_KILLED;
   498                 state = ST_KILLED;
   390                 nd.close(fd);
   499                 nd.close(fd);
   391             }
   500             }
       
   501         } finally {
       
   502             stateLock.unlock();
   392         }
   503         }
   393     }
   504     }
   394 
   505 
   395     /**
   506     /**
   396      * Returns true if channel's socket is bound
   507      * Returns true if channel's socket is bound
   397      */
   508      */
   398     boolean isBound() {
   509     boolean isBound() {
   399         synchronized (stateLock) {
   510         stateLock.lock();
       
   511         try {
   400             return localAddress != null;
   512             return localAddress != null;
       
   513         } finally {
       
   514             stateLock.unlock();
   401         }
   515         }
   402     }
   516     }
   403 
   517 
   404     /**
   518     /**
   405      * Returns the local address, or null if not bound
   519      * Returns the local address, or null if not bound
   406      */
   520      */
   407     InetSocketAddress localAddress() {
   521     InetSocketAddress localAddress() {
   408         synchronized (stateLock) {
   522         stateLock.lock();
       
   523         try {
   409             return localAddress;
   524             return localAddress;
   410         }
   525         } finally {
   411     }
   526             stateLock.unlock();
   412 
       
   413     /**
       
   414      * Poll this channel's socket for a new connection up to the given timeout.
       
   415      * @return {@code true} if there is a connection to accept
       
   416      */
       
   417     boolean pollAccept(long timeout) throws IOException {
       
   418         assert Thread.holdsLock(blockingLock()) && isBlocking();
       
   419         acceptLock.lock();
       
   420         try {
       
   421             boolean polled = false;
       
   422             try {
       
   423                 begin(true);
       
   424                 int events = Net.poll(fd, Net.POLLIN, timeout);
       
   425                 polled = (events != 0);
       
   426             } finally {
       
   427                 end(true, polled);
       
   428             }
       
   429             return polled;
       
   430         } finally {
       
   431             acceptLock.unlock();
       
   432         }
   527         }
   433     }
   528     }
   434 
   529 
   435     /**
   530     /**
   436      * Translates native poll revent set into a ready operation set
   531      * Translates native poll revent set into a ready operation set
   492         sb.append(this.getClass().getName());
   587         sb.append(this.getClass().getName());
   493         sb.append('[');
   588         sb.append('[');
   494         if (!isOpen()) {
   589         if (!isOpen()) {
   495             sb.append("closed");
   590             sb.append("closed");
   496         } else {
   591         } else {
   497             synchronized (stateLock) {
   592             stateLock.lock();
       
   593             try {
   498                 InetSocketAddress addr = localAddress;
   594                 InetSocketAddress addr = localAddress;
   499                 if (addr == null) {
   595                 if (addr == null) {
   500                     sb.append("unbound");
   596                     sb.append("unbound");
   501                 } else {
   597                 } else {
   502                     sb.append(Net.getRevealedLocalAddressAsString(addr));
   598                     sb.append(Net.getRevealedLocalAddressAsString(addr));
   503                 }
   599                 }
       
   600             } finally {
       
   601                 stateLock.unlock();
   504             }
   602             }
   505         }
   603         }
   506         sb.append(']');
   604         sb.append(']');
   507         return sb.toString();
   605         return sb.toString();
   508     }
   606     }