/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * 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 org.apache.ignite.internal.util.ipc.shmem; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.util.ipc.IpcEndpoint; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; /** * IPC endpoint based on shared memory space. */ public class IpcSharedMemoryClientEndpoint implements IpcEndpoint { /** In space. */ private final IpcSharedMemorySpace inSpace; /** Out space. */ private final IpcSharedMemorySpace outSpace; /** In space. */ private final IpcSharedMemoryInputStream in; /** Out space. */ private final IpcSharedMemoryOutputStream out; /** */ private boolean checkIn = true; /** */ private boolean checkOut = true; /** */ private final Thread checker; /** */ private final IgniteLogger log; /** * Creates connected client IPC endpoint. * * @param inSpace In space. * @param outSpace Out space. * @param parent Parent logger. */ public IpcSharedMemoryClientEndpoint(IpcSharedMemorySpace inSpace, IpcSharedMemorySpace outSpace, IgniteLogger parent) { assert inSpace != null; assert outSpace != null; log = parent.getLogger(IpcSharedMemoryClientEndpoint.class); this.inSpace = inSpace; this.outSpace = outSpace; in = new IpcSharedMemoryInputStream(inSpace); out = new IpcSharedMemoryOutputStream(outSpace); checker = null; } /** * Creates and connects client IPC endpoint and starts background checker thread to avoid deadlocks on other party * crash. Waits until port became available. * * @param port Port server endpoint bound to. * @param parent Parent logger. * @throws IgniteCheckedException If connection fails. */ public IpcSharedMemoryClientEndpoint(int port, IgniteLogger parent) throws IgniteCheckedException { this(port, 0, parent); } /** * Creates and connects client IPC endpoint and starts background checker thread to avoid deadlocks on other party * crash. * * @param port Port server endpoint bound to. * @param timeout Connection timeout. * @param parent Parent logger. * @throws IgniteCheckedException If connection fails. */ @SuppressWarnings({"CallToThreadStartDuringObjectConstruction", "ErrorNotRethrown"}) public IpcSharedMemoryClientEndpoint(int port, int timeout, IgniteLogger parent) throws IgniteCheckedException { assert port > 0; assert port < 0xffff; log = parent.getLogger(IpcSharedMemoryClientEndpoint.class); IpcSharedMemorySpace inSpace = null; IpcSharedMemorySpace outSpace = null; Socket sock = new Socket(); Exception err = null; boolean clear = true; try { IpcSharedMemoryNativeLoader.load(log); sock.connect(new InetSocketAddress("127.0.0.1", port), timeout); // Send request. ObjectOutputStream out = new ObjectOutputStream(sock.getOutputStream()); int pid = IpcSharedMemoryUtils.pid(); out.writeObject(new IpcSharedMemoryInitRequest(pid)); ObjectInputStream in = new ObjectInputStream(sock.getInputStream()); IpcSharedMemoryInitResponse res = (IpcSharedMemoryInitResponse)in.readObject(); err = res.error(); if (err == null) { String inTokFileName = res.inTokenFileName(); assert inTokFileName != null; inSpace = new IpcSharedMemorySpace(inTokFileName, res.pid(), pid, res.size(), true, res.inSharedMemoryId(), log); String outTokFileName = res.outTokenFileName(); assert outTokFileName != null; outSpace = new IpcSharedMemorySpace(outTokFileName, pid, res.pid(), res.size(), false, res.outSharedMemoryId(), log); // This is success ACK. out.writeBoolean(true); out.flush(); clear = false; } } catch (UnsatisfiedLinkError e) { throw IpcSharedMemoryUtils.linkError(e); } catch (IOException e) { throw new IgniteCheckedException("Failed to connect shared memory endpoint to port " + "(is shared memory server endpoint up and running?): " + port, e); } catch (ClassNotFoundException | ClassCastException e) { throw new IgniteCheckedException(e); } finally { U.closeQuiet(sock); if (clear) { if (inSpace != null) inSpace.forceClose(); if (outSpace != null) outSpace.forceClose(); } } if (err != null) // Error response. throw new IgniteCheckedException(err); this.inSpace = inSpace; this.outSpace = outSpace; in = new IpcSharedMemoryInputStream(inSpace); out = new IpcSharedMemoryOutputStream(outSpace); checker = new Thread(new AliveChecker()); // Required for Hadoop 2.x checker.setDaemon(true); checker.start(); } /** {@inheritDoc} */ @Override public InputStream inputStream() { return in; } /** {@inheritDoc} */ @Override public OutputStream outputStream() { return out; } /** {@inheritDoc} */ @Override public void close() { U.closeQuiet(in); U.closeQuiet(out); stopChecker(); } /** * Forcibly closes spaces and frees all system resources. <p> This method should be called with caution as it may * result to the other-party process crash. It is intended to call when there was an IO error during handshake and * other party has not yet attached to the space. */ public void forceClose() { in.forceClose(); out.forceClose(); stopChecker(); } /** * */ private void stopChecker() { if (checker != null) { checker.interrupt(); try { checker.join(); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } } } /** @return {@code True} if other party is alive and new invocation of this method needed. */ boolean checkOtherPartyAlive() { if (checkIn) { File tokFile = new File(inSpace.tokenFileName()); if (!tokFile.exists()) checkIn = false; } if (checkOut) { File tokFile = new File(outSpace.tokenFileName()); if (!tokFile.exists()) checkOut = false; } if (!checkIn && !checkOut) return false; if (!IpcSharedMemoryUtils.alive(inSpace.otherPartyPid())) { U.warn(log, "Remote process is considered to be dead (shared memory space will be forcibly closed): " + inSpace.otherPartyPid()); closeSpace(inSpace); closeSpace(outSpace); return false; } // Need to call this method again after timeout. return true; } /** * This method is intended for test purposes only. * * @return In space. */ IpcSharedMemorySpace inSpace() { return inSpace; } /** * This method is intended for test purposes only. * * @return Out space. */ IpcSharedMemorySpace outSpace() { return outSpace; } /** @param space Space to close. */ private void closeSpace(IpcSharedMemorySpace space) { assert space != null; space.forceClose(); File tokFile = new File(space.tokenFileName()); // Space is not usable at this point and all local threads // are guaranteed to leave its methods (other party is not alive). // So, we can cleanup resources without additional synchronization. IpcSharedMemoryUtils.freeSystemResources(tokFile.getAbsolutePath(), space.size()); tokFile.delete(); } /** {@inheritDoc} */ @Override public String toString() { return S.toString(IpcSharedMemoryClientEndpoint.class, this); } /** * */ private class AliveChecker implements Runnable { /** Check frequency. */ private static final long CHECK_FREQ = 10000; /** {@inheritDoc} */ @SuppressWarnings("BusyWait") @Override public void run() { while (!Thread.currentThread().isInterrupted()) { try { Thread.sleep(CHECK_FREQ); } catch (InterruptedException ignored) { return; } if (!checkOtherPartyAlive()) // No need to check any more. return; } } } }