/*
* 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.processors.service;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.cache.Cache;
import javax.cache.event.CacheEntryEvent;
import javax.cache.event.CacheEntryUpdatedListener;
import org.apache.ignite.IgniteCheckedException;
import org.apache.ignite.IgniteException;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.cluster.ClusterGroup;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.compute.ComputeJobContext;
import org.apache.ignite.configuration.DeploymentMode;
import org.apache.ignite.configuration.IgniteConfiguration;
import org.apache.ignite.events.DiscoveryEvent;
import org.apache.ignite.events.EventType;
import org.apache.ignite.internal.GridClosureCallMode;
import org.apache.ignite.internal.GridKernalContext;
import org.apache.ignite.internal.IgniteClientDisconnectedCheckedException;
import org.apache.ignite.internal.IgniteEx;
import org.apache.ignite.internal.IgniteInternalFuture;
import org.apache.ignite.internal.IgniteInterruptedCheckedException;
import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException;
import org.apache.ignite.internal.events.DiscoveryCustomEvent;
import org.apache.ignite.internal.managers.discovery.DiscoCache;
import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage;
import org.apache.ignite.internal.managers.eventstorage.DiscoveryEventListener;
import org.apache.ignite.internal.processors.GridProcessorAdapter;
import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
import org.apache.ignite.internal.processors.cache.CacheAffinityChangeMessage;
import org.apache.ignite.internal.processors.cache.CacheEntryImpl;
import org.apache.ignite.internal.processors.cache.CacheIteratorConverter;
import org.apache.ignite.internal.processors.cache.DynamicCacheChangeBatch;
import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
import org.apache.ignite.internal.processors.cache.binary.MetadataUpdateAcceptedMessage;
import org.apache.ignite.internal.processors.cache.binary.MetadataUpdateProposedMessage;
import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTxLocal;
import org.apache.ignite.internal.processors.cache.query.CacheQuery;
import org.apache.ignite.internal.processors.cache.query.GridCacheQueryManager;
import org.apache.ignite.internal.processors.cluster.IgniteChangeGlobalStateSupport;
import org.apache.ignite.internal.processors.task.GridInternal;
import org.apache.ignite.internal.processors.timeout.GridTimeoutObject;
import org.apache.ignite.internal.util.GridEmptyIterator;
import org.apache.ignite.internal.util.GridSpinBusyLock;
import org.apache.ignite.internal.util.SerializableTransient;
import org.apache.ignite.internal.util.future.GridCompoundFuture;
import org.apache.ignite.internal.util.future.GridFinishedFuture;
import org.apache.ignite.internal.util.future.GridFutureAdapter;
import org.apache.ignite.internal.util.lang.GridCloseableIterator;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.X;
import org.apache.ignite.internal.util.typedef.internal.A;
import org.apache.ignite.internal.util.typedef.internal.CU;
import org.apache.ignite.internal.util.typedef.internal.LT;
import org.apache.ignite.internal.util.typedef.internal.S;
import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteBiPredicate;
import org.apache.ignite.lang.IgniteCallable;
import org.apache.ignite.lang.IgniteFuture;
import org.apache.ignite.lang.IgniteUuid;
import org.apache.ignite.marshaller.Marshaller;
import org.apache.ignite.plugin.security.SecurityPermission;
import org.apache.ignite.resources.IgniteInstanceResource;
import org.apache.ignite.resources.JobContextResource;
import org.apache.ignite.resources.LoggerResource;
import org.apache.ignite.services.Service;
import org.apache.ignite.services.ServiceConfiguration;
import org.apache.ignite.services.ServiceDescriptor;
import org.apache.ignite.thread.IgniteThreadFactory;
import org.jetbrains.annotations.Nullable;
import org.jsr166.ConcurrentHashMap8;
import static org.apache.ignite.IgniteSystemProperties.IGNITE_SERVICES_COMPATIBILITY_MODE;
import static org.apache.ignite.IgniteSystemProperties.getString;
import static org.apache.ignite.configuration.DeploymentMode.ISOLATED;
import static org.apache.ignite.configuration.DeploymentMode.PRIVATE;
import static org.apache.ignite.internal.IgniteNodeAttributes.ATTR_SERVICES_COMPATIBILITY_MODE;
import static org.apache.ignite.internal.processors.cache.GridCacheUtils.UTILITY_CACHE_NAME;
import static org.apache.ignite.transactions.TransactionConcurrency.PESSIMISTIC;
import static org.apache.ignite.transactions.TransactionIsolation.REPEATABLE_READ;
/**
* Grid service processor.
*/
@SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter", "ConstantConditions"})
public class GridServiceProcessor extends GridProcessorAdapter implements IgniteChangeGlobalStateSupport {
/** */
private final Boolean srvcCompatibilitySysProp;
/** Time to wait before reassignment retries. */
private static final long RETRY_TIMEOUT = 1000;
/** */
private static final int[] EVTS = {
EventType.EVT_NODE_JOINED,
EventType.EVT_NODE_LEFT,
EventType.EVT_NODE_FAILED,
DiscoveryCustomEvent.EVT_DISCOVERY_CUSTOM_EVT
};
/** */
private final AtomicReference<ServicesCompatibilityState> compatibilityState;
/** Local service instances. */
private final Map<String, Collection<ServiceContextImpl>> locSvcs = new HashMap<>();
/** Deployment futures. */
private final ConcurrentMap<String, GridServiceDeploymentFuture> depFuts = new ConcurrentHashMap8<>();
/** Deployment futures. */
private final ConcurrentMap<String, GridFutureAdapter<?>> undepFuts = new ConcurrentHashMap8<>();
/** Pending compute job contexts that waiting for utility cache initialization. */
private final List<ComputeJobContext> pendingJobCtxs = new ArrayList<>(0);
/** Deployment executor service. */
private volatile ExecutorService depExe;
/** Busy lock. */
private final GridSpinBusyLock busyLock = new GridSpinBusyLock();
/** Thread factory. */
private ThreadFactory threadFactory = new IgniteThreadFactory(ctx.igniteInstanceName());
/** Thread local for service name. */
private ThreadLocal<String> svcName = new ThreadLocal<>();
/** Service cache. */
private IgniteInternalCache<Object, Object> cache;
/** Topology listener. */
private DiscoveryEventListener topLsnr = new TopologyListener();
/**
* @param ctx Kernal context.
*/
public GridServiceProcessor(GridKernalContext ctx) {
super(ctx);
depExe = Executors.newSingleThreadExecutor(new IgniteThreadFactory(ctx.igniteInstanceName(), "srvc-deploy"));
String servicesCompatibilityMode = getString(IGNITE_SERVICES_COMPATIBILITY_MODE);
srvcCompatibilitySysProp = servicesCompatibilityMode == null ? null : Boolean.valueOf(servicesCompatibilityMode);
compatibilityState = new AtomicReference<>(
new ServicesCompatibilityState(srvcCompatibilitySysProp != null ? srvcCompatibilitySysProp : false, false));
}
/**
* @param ctx Context.
* @throws IgniteCheckedException If failed.
*/
public void onContinuousProcessorStarted(GridKernalContext ctx) throws IgniteCheckedException {
if (ctx.clientNode()) {
assert !ctx.isDaemon();
ctx.continuous().registerStaticRoutine(
CU.UTILITY_CACHE_NAME, new ServiceEntriesListener(), null, null
);
}
}
/** {@inheritDoc} */
@Override public void start(boolean activeOnStart) throws IgniteCheckedException {
ctx.addNodeAttribute(ATTR_SERVICES_COMPATIBILITY_MODE, srvcCompatibilitySysProp);
if (ctx.isDaemon() || !activeOnStart)
return;
IgniteConfiguration cfg = ctx.config();
DeploymentMode depMode = cfg.getDeploymentMode();
if (cfg.isPeerClassLoadingEnabled() && (depMode == PRIVATE || depMode == ISOLATED) &&
!F.isEmpty(cfg.getServiceConfiguration()))
throw new IgniteCheckedException("Cannot deploy services in PRIVATE or ISOLATED deployment mode: " + depMode);
}
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override public void onKernalStart(boolean activeOnStart) throws IgniteCheckedException {
if (ctx.isDaemon() || !ctx.state().active())
return;
cache = ctx.cache().utilityCache();
if (!ctx.clientNode())
ctx.event().addDiscoveryEventListener(topLsnr, EVTS);
try {
if (ctx.deploy().enabled())
ctx.cache().context().deploy().ignoreOwnership(true);
if (!ctx.clientNode()) {
assert cache.context().affinityNode();
cache.context().continuousQueries().executeInternalQuery(
new ServiceEntriesListener(), null, true, true, false
);
}
else {
assert !ctx.isDaemon();
ctx.closure().runLocalSafe(new Runnable() {
@Override public void run() {
try {
Iterable<CacheEntryEvent<?, ?>> entries =
cache.context().continuousQueries().existingEntries(false, null);
onSystemCacheUpdated(entries);
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to load service entries: " + e, e);
}
}
});
}
}
finally {
if (ctx.deploy().enabled())
ctx.cache().context().deploy().ignoreOwnership(false);
}
ServiceConfiguration[] cfgs = ctx.config().getServiceConfiguration();
if (cfgs != null) {
Collection<IgniteInternalFuture<?>> futs = new ArrayList<>();
for (ServiceConfiguration c : cfgs) {
// Deploy only on server nodes by default.
if (c.getNodeFilter() == null)
c.setNodeFilter(ctx.cluster().get().forServers().predicate());
futs.add(deploy(c));
}
// Await for services to deploy.
for (IgniteInternalFuture<?> f : futs)
f.get();
}
if (log.isDebugEnabled())
log.debug("Started service processor.");
}
/** {@inheritDoc} */
@Override public void onKernalStop(boolean cancel) {
if (ctx.isDaemon())
return;
busyLock.block();
try {
U.shutdownNow(GridServiceProcessor.class, depExe, log);
if (!ctx.clientNode())
ctx.event().removeDiscoveryEventListener(topLsnr);
Collection<ServiceContextImpl> ctxs = new ArrayList<>();
synchronized (locSvcs) {
for (Collection<ServiceContextImpl> ctxs0 : locSvcs.values())
ctxs.addAll(ctxs0);
}
for (ServiceContextImpl ctx : ctxs) {
ctx.setCancelled(true);
Service svc = ctx.service();
if (svc != null)
svc.cancel(ctx);
ctx.executor().shutdownNow();
}
for (ServiceContextImpl ctx : ctxs) {
try {
if (log.isInfoEnabled() && !ctxs.isEmpty())
log.info("Shutting down distributed service [name=" + ctx.name() + ", execId8=" +
U.id8(ctx.executionId()) + ']');
ctx.executor().awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
}
catch (InterruptedException ignore) {
Thread.currentThread().interrupt();
U.error(log, "Got interrupted while waiting for service to shutdown (will continue stopping node): " +
ctx.name());
}
}
Exception err = new IgniteCheckedException("Operation has been cancelled (node is stopping).");
cancelFutures(depFuts, err);
cancelFutures(undepFuts, err);
}
finally {
busyLock.unblock();
}
if (log.isDebugEnabled())
log.debug("Stopped service processor.");
}
/** {@inheritDoc} */
@Override public void onActivate(GridKernalContext kctx) throws IgniteCheckedException {
if (log.isDebugEnabled())
log.debug("Activate service processor [nodeId=" + ctx.localNodeId() +
" topVer=" + ctx.discovery().topologyVersionEx() + " ]");
depExe = Executors.newSingleThreadExecutor(new IgniteThreadFactory(ctx.igniteInstanceName(), "srvc-deploy"));
start(true);
onKernalStart(true);
}
/** {@inheritDoc} */
@Override public void onDeActivate(GridKernalContext kctx) throws IgniteCheckedException {
if (log.isDebugEnabled())
log.debug("DeActivate service processor [nodeId=" + ctx.localNodeId() +
" topVer=" + ctx.discovery().topologyVersionEx() + " ]");
cancelFutures(depFuts, new IgniteCheckedException("Failed to deploy service, cluster in active."));
cancelFutures(undepFuts, new IgniteCheckedException("Failed to undeploy service, cluster in active."));
onKernalStop(true);
}
/** {@inheritDoc} */
@Override public void onDisconnected(IgniteFuture<?> reconnectFut) throws IgniteCheckedException {
cancelFutures(depFuts, new IgniteClientDisconnectedCheckedException(ctx.cluster().clientReconnectFuture(),
"Failed to deploy service, client node disconnected."));
cancelFutures(undepFuts, new IgniteClientDisconnectedCheckedException(ctx.cluster().clientReconnectFuture(),
"Failed to undeploy service, client node disconnected."));
}
/**
* @param futs Futs.
* @param err Exception.
*/
private void cancelFutures(ConcurrentMap<String, ? extends GridFutureAdapter<?>> futs, Exception err) {
for (Map.Entry<String, ? extends GridFutureAdapter<?>> entry : futs.entrySet()) {
GridFutureAdapter fut = entry.getValue();
fut.onDone(err);
futs.remove(entry.getKey(), fut);
}
}
/**
* Validates service configuration.
*
* @param c Service configuration.
* @throws IgniteException If validation failed.
*/
private void validate(ServiceConfiguration c) throws IgniteException {
IgniteConfiguration cfg = ctx.config();
DeploymentMode depMode = cfg.getDeploymentMode();
if (cfg.isPeerClassLoadingEnabled() && (depMode == PRIVATE || depMode == ISOLATED))
throw new IgniteException("Cannot deploy services in PRIVATE or ISOLATED deployment mode: " + depMode);
ensure(c.getName() != null, "getName() != null", null);
ensure(c.getTotalCount() >= 0, "getTotalCount() >= 0", c.getTotalCount());
ensure(c.getMaxPerNodeCount() >= 0, "getMaxPerNodeCount() >= 0", c.getMaxPerNodeCount());
ensure(c.getService() != null, "getService() != null", c.getService());
ensure(c.getTotalCount() > 0 || c.getMaxPerNodeCount() > 0,
"c.getTotalCount() > 0 || c.getMaxPerNodeCount() > 0", null);
}
/**
* @param cond Condition.
* @param desc Description.
* @param v Value.
*/
private void ensure(boolean cond, String desc, @Nullable Object v) {
if (!cond)
if (v != null)
throw new IgniteException("Service configuration check failed (" + desc + "): " + v);
else
throw new IgniteException("Service configuration check failed (" + desc + ")");
}
/**
* @param name Service name.
* @param svc Service.
* @return Future.
*/
public IgniteInternalFuture<?> deployNodeSingleton(ClusterGroup prj, String name, Service svc) {
return deployMultiple(prj, name, svc, 0, 1);
}
/**
* @param name Service name.
* @param svc Service.
* @return Future.
*/
public IgniteInternalFuture<?> deployClusterSingleton(ClusterGroup prj, String name, Service svc) {
return deployMultiple(prj, name, svc, 1, 1);
}
/**
* @param name Service name.
* @param svc Service.
* @param totalCnt Total count.
* @param maxPerNodeCnt Max per-node count.
* @return Future.
*/
public IgniteInternalFuture<?> deployMultiple(ClusterGroup prj, String name, Service svc, int totalCnt,
int maxPerNodeCnt) {
ServiceConfiguration cfg = new ServiceConfiguration();
cfg.setName(name);
cfg.setService(svc);
cfg.setTotalCount(totalCnt);
cfg.setMaxPerNodeCount(maxPerNodeCnt);
cfg.setNodeFilter(F.<ClusterNode>alwaysTrue() == prj.predicate() ? null : prj.predicate());
return deploy(cfg);
}
/**
* @param name Service name.
* @param svc Service.
* @param cacheName Cache name.
* @param affKey Affinity key.
* @return Future.
*/
public IgniteInternalFuture<?> deployKeyAffinitySingleton(String name, Service svc, String cacheName, Object affKey) {
A.notNull(affKey, "affKey");
ServiceConfiguration cfg = new ServiceConfiguration();
cfg.setName(name);
cfg.setService(svc);
cfg.setCacheName(cacheName);
cfg.setAffinityKey(affKey);
cfg.setTotalCount(1);
cfg.setMaxPerNodeCount(1);
return deploy(cfg);
}
/**
* @param cfg Service configuration.
* @return Future for deployment.
*/
public IgniteInternalFuture<?> deploy(ServiceConfiguration cfg) {
A.notNull(cfg, "cfg");
ServicesCompatibilityState state = markCompatibilityStateAsUsed();
validate(cfg);
ctx.security().authorize(cfg.getName(), SecurityPermission.SERVICE_DEPLOY, null);
if (!state.srvcCompatibility) {
Marshaller marsh = ctx.config().getMarshaller();
LazyServiceConfiguration cfg0;
try {
byte[] srvcBytes = U.marshal(marsh, cfg.getService());
cfg0 = new LazyServiceConfiguration(cfg, srvcBytes);
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to marshal service with configured marshaller [srvc=" + cfg.getService()
+ ", marsh=" + marsh + "]", e);
return new GridFinishedFuture<>(e);
}
cfg = cfg0;
}
GridServiceDeploymentFuture fut = new GridServiceDeploymentFuture(cfg);
GridServiceDeploymentFuture old = depFuts.putIfAbsent(cfg.getName(), fut);
if (old != null) {
if (!old.configuration().equalsIgnoreNodeFilter(cfg)) {
fut.onDone(new IgniteCheckedException("Failed to deploy service (service already exists with " +
"different configuration) [deployed=" + old.configuration() + ", new=" + cfg + ']'));
return fut;
}
return old;
}
if (ctx.clientDisconnected()) {
fut.onDone(new IgniteClientDisconnectedCheckedException(ctx.cluster().clientReconnectFuture(),
"Failed to deploy service, client node disconnected."));
depFuts.remove(cfg.getName(), fut);
}
while (true) {
try {
GridServiceDeploymentKey key = new GridServiceDeploymentKey(cfg.getName());
if (ctx.deploy().enabled())
ctx.cache().context().deploy().ignoreOwnership(true);
try {
GridServiceDeployment dep = (GridServiceDeployment)cache.getAndPutIfAbsent(key,
new GridServiceDeployment(ctx.localNodeId(), cfg));
if (dep != null) {
if (!dep.configuration().equalsIgnoreNodeFilter(cfg)) {
// Remove future from local map.
depFuts.remove(cfg.getName(), fut);
fut.onDone(new IgniteCheckedException("Failed to deploy service (service already exists with " +
"different configuration) [deployed=" + dep.configuration() + ", new=" + cfg + ']'));
}
else {
Iterator<Cache.Entry<Object, Object>> it = serviceEntries(
ServiceAssignmentsPredicate.INSTANCE);
while (it.hasNext()) {
Cache.Entry<Object, Object> e = it.next();
GridServiceAssignments assigns = (GridServiceAssignments)e.getValue();
if (assigns.name().equals(cfg.getName())) {
// Remove future from local map.
depFuts.remove(cfg.getName(), fut);
fut.onDone();
break;
}
}
if (!dep.configuration().equalsIgnoreNodeFilter(cfg))
U.warn(log, "Service already deployed with different configuration (will ignore) " +
"[deployed=" + dep.configuration() + ", new=" + cfg + ']');
}
}
}
finally {
if (ctx.deploy().enabled())
ctx.cache().context().deploy().ignoreOwnership(false);
}
return fut;
}
catch (ClusterTopologyCheckedException e) {
if (log.isDebugEnabled())
log.debug("Topology changed while deploying service (will retry): " + e.getMessage());
}
catch (IgniteCheckedException e) {
if (e.hasCause(ClusterTopologyCheckedException.class)) {
if (log.isDebugEnabled())
log.debug("Topology changed while deploying service (will retry): " + e.getMessage());
continue;
}
U.error(log, "Failed to deploy service: " + cfg.getName(), e);
return new GridFinishedFuture<>(e);
}
}
}
/**
* @return Compatibility state.
*/
private ServicesCompatibilityState markCompatibilityStateAsUsed() {
while (true) {
ServicesCompatibilityState state = compatibilityState.get();
if (state.used)
return state;
ServicesCompatibilityState newState = new ServicesCompatibilityState(state.srvcCompatibility, true);
if (compatibilityState.compareAndSet(state, newState))
return newState;
}
}
/**
* @param name Service name.
* @return Future.
*/
public IgniteInternalFuture<?> cancel(String name) {
ctx.security().authorize(name, SecurityPermission.SERVICE_CANCEL, null);
while (true) {
try {
GridFutureAdapter<?> fut = new GridFutureAdapter<>();
GridFutureAdapter<?> old;
if ((old = undepFuts.putIfAbsent(name, fut)) != null)
fut = old;
else {
GridServiceDeploymentKey key = new GridServiceDeploymentKey(name);
if (cache.getAndRemove(key) == null) {
// Remove future from local map if service was not deployed.
undepFuts.remove(name);
fut.onDone();
}
}
return fut;
}
catch (ClusterTopologyCheckedException e) {
if (log.isDebugEnabled())
log.debug("Topology changed while deploying service (will retry): " + e.getMessage());
}
catch (IgniteCheckedException e) {
log.error("Failed to undeploy service: " + name, e);
return new GridFinishedFuture<>(e);
}
}
}
/**
* @return Future.
*/
@SuppressWarnings("unchecked")
public IgniteInternalFuture<?> cancelAll() {
Iterator<Cache.Entry<Object, Object>> it = serviceEntries(ServiceDeploymentPredicate.INSTANCE);
GridCompoundFuture res = null;
while (it.hasNext()) {
Cache.Entry<Object, Object> e = it.next();
GridServiceDeployment dep = (GridServiceDeployment)e.getValue();
if (res == null)
res = new GridCompoundFuture<>();
// Cancel each service separately.
res.add(cancel(dep.configuration().getName()));
}
if (res != null) {
res.markInitialized();
return res;
}
else
return new GridFinishedFuture<>();
}
/**
* @param name Service name.
* @param timeout If greater than 0 limits task execution time. Cannot be negative.
* @return Service topology.
* @throws IgniteCheckedException On error.
*/
public Map<UUID, Integer> serviceTopology(String name, long timeout) throws IgniteCheckedException {
ClusterNode node = cache.affinity().mapKeyToNode(name);
final ServiceTopologyCallable call = new ServiceTopologyCallable(name);
return ctx.closure().callAsyncNoFailover(
GridClosureCallMode.BROADCAST,
call,
Collections.singletonList(node),
false,
timeout
).get();
}
/**
* @param cache Utility cache.
* @param svcName Service name.
* @return Service topology.
* @throws IgniteCheckedException In case of error.
*/
private static Map<UUID, Integer> serviceTopology(IgniteInternalCache<Object, Object> cache, String svcName)
throws IgniteCheckedException {
GridServiceAssignments val = (GridServiceAssignments)cache.get(new GridServiceAssignmentsKey(svcName));
return val != null ? val.assigns() : null;
}
/**
* @return Collection of service descriptors.
*/
public Collection<ServiceDescriptor> serviceDescriptors() {
Collection<ServiceDescriptor> descs = new ArrayList<>();
Iterator<Cache.Entry<Object, Object>> it = serviceEntries(ServiceDeploymentPredicate.INSTANCE);
while (it.hasNext()) {
Cache.Entry<Object, Object> e = it.next();
GridServiceDeployment dep = (GridServiceDeployment)e.getValue();
ServiceDescriptorImpl desc = new ServiceDescriptorImpl(dep);
try {
GridServiceAssignments assigns = (GridServiceAssignments)cache.getForcePrimary(
new GridServiceAssignmentsKey(dep.configuration().getName()));
if (assigns != null) {
desc.topologySnapshot(assigns.assigns());
descs.add(desc);
}
}
catch (IgniteCheckedException ex) {
log.error("Failed to get assignments from replicated cache for service: " +
dep.configuration().getName(), ex);
}
}
return descs;
}
/**
* @param name Service name.
* @param <T> Service type.
* @return Service by specified service name.
*/
@SuppressWarnings("unchecked")
public <T> T service(String name) {
ctx.security().authorize(name, SecurityPermission.SERVICE_INVOKE, null);
Collection<ServiceContextImpl> ctxs;
synchronized (locSvcs) {
ctxs = locSvcs.get(name);
}
if (ctxs == null)
return null;
synchronized (ctxs) {
if (ctxs.isEmpty())
return null;
for (ServiceContextImpl ctx : ctxs) {
Service svc = ctx.service();
if (svc != null)
return (T)svc;
}
return null;
}
}
/**
* @param name Service name.
* @return Service by specified service name.
*/
public ServiceContextImpl serviceContext(String name) {
Collection<ServiceContextImpl> ctxs;
synchronized (locSvcs) {
ctxs = locSvcs.get(name);
}
if (ctxs == null)
return null;
synchronized (ctxs) {
if (ctxs.isEmpty())
return null;
for (ServiceContextImpl ctx : ctxs) {
if (ctx.service() != null)
return ctx;
}
return null;
}
}
/**
* @param prj Grid projection.
* @param name Service name.
* @param svcItf Service class.
* @param sticky Whether multi-node request should be done.
* @param timeout If greater than 0 limits service acquire time. Cannot be negative.
* @param <T> Service interface type.
* @return The proxy of a service by its name and class.
* @throws IgniteException If failed to create proxy.
*/
@SuppressWarnings("unchecked")
public <T> T serviceProxy(ClusterGroup prj, String name, Class<? super T> svcItf, boolean sticky, long timeout)
throws IgniteException {
ctx.security().authorize(name, SecurityPermission.SERVICE_INVOKE, null);
if (hasLocalNode(prj)) {
ServiceContextImpl ctx = serviceContext(name);
if (ctx != null) {
Service svc = ctx.service();
if (svc != null) {
if (!svcItf.isAssignableFrom(svc.getClass()))
throw new IgniteException("Service does not implement specified interface [svcItf=" +
svcItf.getName() + ", svcCls=" + svc.getClass().getName() + ']');
return (T)svc;
}
}
}
return new GridServiceProxy<T>(prj, name, svcItf, sticky, timeout, ctx).proxy();
}
/**
* @param prj Grid nodes projection.
* @return Whether given projection contains any local node.
*/
private boolean hasLocalNode(ClusterGroup prj) {
for (ClusterNode n : prj.nodes()) {
if (n.isLocal())
return true;
}
return false;
}
/**
* @param name Service name.
* @param <T> Service type.
* @return Services by specified service name.
*/
@SuppressWarnings("unchecked")
public <T> Collection<T> services(String name) {
ctx.security().authorize(name, SecurityPermission.SERVICE_INVOKE, null);
Collection<ServiceContextImpl> ctxs;
synchronized (locSvcs) {
ctxs = locSvcs.get(name);
}
if (ctxs == null)
return null;
synchronized (ctxs) {
Collection<T> res = new ArrayList<>(ctxs.size());
for (ServiceContextImpl ctx : ctxs) {
Service svc = ctx.service();
if (svc != null)
res.add((T)svc);
}
return res;
}
}
/**
* Reassigns service to nodes.
*
* @param dep Service deployment.
* @param topVer Topology version.
* @throws IgniteCheckedException If failed.
*/
private void reassign(GridServiceDeployment dep, AffinityTopologyVersion topVer) throws IgniteCheckedException {
ServiceConfiguration cfg = dep.configuration();
Object nodeFilter = cfg.getNodeFilter();
if (nodeFilter != null)
ctx.resource().injectGeneric(nodeFilter);
int totalCnt = cfg.getTotalCount();
int maxPerNodeCnt = cfg.getMaxPerNodeCount();
String cacheName = cfg.getCacheName();
Object affKey = cfg.getAffinityKey();
while (true) {
GridServiceAssignments assigns = new GridServiceAssignments(cfg, dep.nodeId(), topVer.topologyVersion());
Collection<ClusterNode> nodes;
// Call node filter outside of transaction.
if (affKey == null) {
nodes = ctx.discovery().nodes(topVer);
if (assigns.nodeFilter() != null) {
Collection<ClusterNode> nodes0 = new ArrayList<>();
for (ClusterNode node : nodes) {
if (assigns.nodeFilter().apply(node))
nodes0.add(node);
}
nodes = nodes0;
}
}
else
nodes = null;
try (GridNearTxLocal tx = cache.txStartEx(PESSIMISTIC, REPEATABLE_READ)) {
GridServiceAssignmentsKey key = new GridServiceAssignmentsKey(cfg.getName());
GridServiceAssignments oldAssigns = (GridServiceAssignments)cache.get(key);
Map<UUID, Integer> cnts = new HashMap<>();
if (affKey != null) {
ClusterNode n = ctx.affinity().mapKeyToNode(cacheName, affKey, topVer);
if (n != null) {
int cnt = maxPerNodeCnt == 0 ? totalCnt == 0 ? 1 : totalCnt : maxPerNodeCnt;
cnts.put(n.id(), cnt);
}
}
else {
if (!nodes.isEmpty()) {
int size = nodes.size();
int perNodeCnt = totalCnt != 0 ? totalCnt / size : maxPerNodeCnt;
int remainder = totalCnt != 0 ? totalCnt % size : 0;
if (perNodeCnt >= maxPerNodeCnt && maxPerNodeCnt != 0) {
perNodeCnt = maxPerNodeCnt;
remainder = 0;
}
for (ClusterNode n : nodes)
cnts.put(n.id(), perNodeCnt);
assert perNodeCnt >= 0;
assert remainder >= 0;
if (remainder > 0) {
int cnt = perNodeCnt + 1;
if (oldAssigns != null) {
Collection<UUID> used = new HashSet<>();
// Avoid redundant moving of services.
for (Map.Entry<UUID, Integer> e : oldAssigns.assigns().entrySet()) {
// Do not assign services to left nodes.
if (ctx.discovery().node(e.getKey()) == null)
continue;
// If old count and new count match, then reuse the assignment.
if (e.getValue() == cnt) {
cnts.put(e.getKey(), cnt);
used.add(e.getKey());
if (--remainder == 0)
break;
}
}
if (remainder > 0) {
List<Map.Entry<UUID, Integer>> entries = new ArrayList<>(cnts.entrySet());
// Randomize.
Collections.shuffle(entries);
for (Map.Entry<UUID, Integer> e : entries) {
// Assign only the ones that have not been reused from previous assignments.
if (!used.contains(e.getKey())) {
if (e.getValue() < maxPerNodeCnt || maxPerNodeCnt == 0) {
e.setValue(e.getValue() + 1);
if (--remainder == 0)
break;
}
}
}
}
}
else {
List<Map.Entry<UUID, Integer>> entries = new ArrayList<>(cnts.entrySet());
// Randomize.
Collections.shuffle(entries);
for (Map.Entry<UUID, Integer> e : entries) {
e.setValue(e.getValue() + 1);
if (--remainder == 0)
break;
}
}
}
}
}
assigns.assigns(cnts);
cache.put(key, assigns);
tx.commit();
break;
}
catch (ClusterTopologyCheckedException e) {
if (log.isDebugEnabled())
log.debug("Topology changed while reassigning (will retry): " + e.getMessage());
U.sleep(10);
}
}
}
/**
* Redeploys local services based on assignments.
*
* @param assigns Assignments.
*/
private void redeploy(GridServiceAssignments assigns) {
String svcName = assigns.name();
Integer assignCnt = assigns.assigns().get(ctx.localNodeId());
if (assignCnt == null)
assignCnt = 0;
Collection<ServiceContextImpl> ctxs;
synchronized (locSvcs) {
ctxs = locSvcs.get(svcName);
if (ctxs == null)
locSvcs.put(svcName, ctxs = new ArrayList<>());
}
Collection<ServiceContextImpl> toInit = new ArrayList<>();
synchronized (ctxs) {
if (ctxs.size() > assignCnt) {
int cancelCnt = ctxs.size() - assignCnt;
cancel(ctxs, cancelCnt);
}
else if (ctxs.size() < assignCnt) {
int createCnt = assignCnt - ctxs.size();
for (int i = 0; i < createCnt; i++) {
ServiceContextImpl svcCtx = new ServiceContextImpl(assigns.name(),
UUID.randomUUID(),
assigns.cacheName(),
assigns.affinityKey(),
Executors.newSingleThreadExecutor(threadFactory));
ctxs.add(svcCtx);
toInit.add(svcCtx);
}
}
}
for (final ServiceContextImpl svcCtx : toInit) {
final Service svc;
try {
svc = copyAndInject(assigns.configuration());
// Initialize service.
svc.init(svcCtx);
svcCtx.service(svc);
}
catch (Throwable e) {
U.error(log, "Failed to initialize service (service will not be deployed): " + assigns.name(), e);
synchronized (ctxs) {
ctxs.removeAll(toInit);
}
if (e instanceof Error)
throw (Error)e;
if (e instanceof RuntimeException)
throw (RuntimeException)e;
return;
}
if (log.isInfoEnabled())
log.info("Starting service instance [name=" + svcCtx.name() + ", execId=" +
svcCtx.executionId() + ']');
// Start service in its own thread.
final ExecutorService exe = svcCtx.executor();
exe.execute(new Runnable() {
@Override public void run() {
try {
svc.execute(svcCtx);
}
catch (InterruptedException | IgniteInterruptedCheckedException ignore) {
if (log.isDebugEnabled())
log.debug("Service thread was interrupted [name=" + svcCtx.name() + ", execId=" +
svcCtx.executionId() + ']');
}
catch (IgniteException e) {
if (e.hasCause(InterruptedException.class) ||
e.hasCause(IgniteInterruptedCheckedException.class)) {
if (log.isDebugEnabled())
log.debug("Service thread was interrupted [name=" + svcCtx.name() +
", execId=" + svcCtx.executionId() + ']');
}
else {
U.error(log, "Service execution stopped with error [name=" + svcCtx.name() +
", execId=" + svcCtx.executionId() + ']', e);
}
}
catch (Throwable e) {
log.error("Service execution stopped with error [name=" + svcCtx.name() +
", execId=" + svcCtx.executionId() + ']', e);
if (e instanceof Error)
throw (Error)e;
}
finally {
// Suicide.
exe.shutdownNow();
}
}
});
}
}
/**
* @param cfg Service configuration.
* @return Copy of service.
* @throws IgniteCheckedException If failed.
*/
private Service copyAndInject(ServiceConfiguration cfg) throws IgniteCheckedException {
Marshaller m = ctx.config().getMarshaller();
if (cfg instanceof LazyServiceConfiguration) {
byte[] bytes = ((LazyServiceConfiguration)cfg).serviceBytes();
Service srvc = U.unmarshal(m, bytes, U.resolveClassLoader(null, ctx.config()));
ctx.resource().inject(srvc);
return srvc;
}
else {
Service svc = cfg.getService();
try {
byte[] bytes = U.marshal(m, svc);
Service cp = U.unmarshal(m, bytes, U.resolveClassLoader(svc.getClass().getClassLoader(), ctx.config()));
ctx.resource().inject(cp);
return cp;
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to copy service (will reuse same instance): " + svc.getClass(), e);
return svc;
}
}
}
/**
* @param ctxs Contexts to cancel.
* @param cancelCnt Number of contexts to cancel.
*/
private void cancel(Iterable<ServiceContextImpl> ctxs, int cancelCnt) {
for (Iterator<ServiceContextImpl> it = ctxs.iterator(); it.hasNext();) {
ServiceContextImpl svcCtx = it.next();
// Flip cancelled flag.
svcCtx.setCancelled(true);
// Notify service about cancellation.
Service svc = svcCtx.service();
if (svc != null) {
try {
svc.cancel(svcCtx);
}
catch (Throwable e) {
log.error("Failed to cancel service (ignoring) [name=" + svcCtx.name() +
", execId=" + svcCtx.executionId() + ']', e);
if (e instanceof Error)
throw e;
}
finally {
try {
ctx.resource().cleanup(svc);
}
catch (IgniteCheckedException e) {
U.error(log, "Failed to clean up service (will ignore): " + svcCtx.name(), e);
}
}
}
// Close out executor thread for the service.
// This will cause the thread to be interrupted.
svcCtx.executor().shutdownNow();
it.remove();
if (log.isInfoEnabled())
log.info("Cancelled service instance [name=" + svcCtx.name() + ", execId=" +
svcCtx.executionId() + ']');
if (--cancelCnt == 0)
break;
}
}
/**
* @param p Entry predicate used to execute query from client node.
* @return Service deployment entries.
*/
@SuppressWarnings("unchecked")
private Iterator<Cache.Entry<Object, Object>> serviceEntries(IgniteBiPredicate<Object, Object> p) {
try {
GridCacheQueryManager qryMgr = cache.context().queries();
CacheQuery<Map.Entry<Object, Object>> qry = qryMgr.createScanQuery(p, null, false);
qry.keepAll(false);
if (!cache.context().affinityNode()) {
ClusterNode oldestSrvNode =
ctx.discovery().oldestAliveCacheServerNode(AffinityTopologyVersion.NONE);
if (oldestSrvNode == null)
return new GridEmptyIterator<>();
qry.projection(ctx.cluster().get().forNode(oldestSrvNode));
}
else
qry.projection(ctx.cluster().get().forLocal());
GridCloseableIterator<Map.Entry<Object, Object>> iter = qry.executeScanQuery();
return cache.context().itHolder().iterator(iter,
new CacheIteratorConverter<Cache.Entry<Object, Object>, Map.Entry<Object,Object>>() {
@Override protected Cache.Entry<Object, Object> convert(Map.Entry<Object, Object> e) {
return new CacheEntryImpl<>(e.getKey(), e.getValue());
}
@Override protected void remove(Cache.Entry<Object, Object> item) {
throw new UnsupportedOperationException();
}
});
}
catch (IgniteCheckedException e) {
throw new IgniteException(e);
}
}
/**
* @param nodes Remote nodes.
*/
public void initCompatibilityMode(Collection<ClusterNode> nodes) {
boolean mode = false;
if (srvcCompatibilitySysProp != null)
mode = srvcCompatibilitySysProp;
while (true) {
ServicesCompatibilityState state = compatibilityState.get();
if (compatibilityState.compareAndSet(state, new ServicesCompatibilityState(mode, state.used)))
return;
}
}
/**
* Called right after utility cache is started and ready for the usage.
*/
public void onUtilityCacheStarted() {
synchronized (pendingJobCtxs) {
if (pendingJobCtxs.size() == 0)
return;
Iterator<ComputeJobContext> iter = pendingJobCtxs.iterator();
while (iter.hasNext()) {
iter.next().callcc();
iter.remove();
}
}
}
/**
* Service deployment listener.
*/
@SuppressWarnings("unchecked")
private class ServiceEntriesListener implements CacheEntryUpdatedListener<Object, Object> {
/** {@inheritDoc} */
@Override public void onUpdated(final Iterable<CacheEntryEvent<?, ?>> deps) {
if (!busyLock.enterBusy())
return;
try {
depExe.execute(new DepRunnable() {
@Override public void run0() {
onSystemCacheUpdated(deps);
}
});
}
finally {
busyLock.leaveBusy();
}
}
}
/**
* @param evts Update events.
*/
private void onSystemCacheUpdated(final Iterable<CacheEntryEvent<?, ?>> evts) {
boolean firstTime = true;
for (CacheEntryEvent<?, ?> e : evts) {
if (e.getKey() instanceof GridServiceDeploymentKey) {
if (firstTime) {
markCompatibilityStateAsUsed();
firstTime = false;
}
processDeployment((CacheEntryEvent)e);
}
else if (e.getKey() instanceof GridServiceAssignmentsKey) {
if (firstTime) {
markCompatibilityStateAsUsed();
firstTime = false;
}
processAssignment((CacheEntryEvent)e);
}
}
}
/**
* @param e Entry.
*/
private void processDeployment(CacheEntryEvent<GridServiceDeploymentKey, GridServiceDeployment> e) {
GridServiceDeployment dep;
try {
dep = e.getValue();
}
catch (IgniteException ex) {
if (X.hasCause(ex, ClassNotFoundException.class))
return;
else
throw ex;
}
if (dep != null) {
svcName.set(dep.configuration().getName());
// Ignore other utility cache events.
AffinityTopologyVersion topVer = ctx.discovery().topologyVersionEx();
ClusterNode oldest = U.oldest(ctx.discovery().nodes(topVer), null);
if (oldest.isLocal())
onDeployment(dep, topVer);
}
// Handle undeployment.
else {
String name = e.getKey().name();
undeploy(name);
// Finish deployment futures if undeployment happened.
GridFutureAdapter<?> fut = depFuts.remove(name);
if (fut != null)
fut.onDone();
// Complete undeployment future.
fut = undepFuts.remove(name);
if (fut != null)
fut.onDone();
GridServiceAssignmentsKey key = new GridServiceAssignmentsKey(name);
// Remove assignment on primary node in case of undeploy.
if (cache.cache().affinity().isPrimary(ctx.discovery().localNode(), key)) {
try {
cache.getAndRemove(key);
}
catch (IgniteCheckedException ex) {
U.error(log, "Failed to remove assignments for undeployed service: " + name, ex);
}
}
}
}
/**
* Deployment callback.
*
* @param dep Service deployment.
* @param topVer Topology version.
*/
private void onDeployment(final GridServiceDeployment dep, final AffinityTopologyVersion topVer) {
// Retry forever.
try {
AffinityTopologyVersion newTopVer = ctx.discovery().topologyVersionEx();
// If topology version changed, reassignment will happen from topology event.
if (newTopVer.equals(topVer))
reassign(dep, topVer);
}
catch (IgniteCheckedException e) {
if (!(e instanceof ClusterTopologyCheckedException))
log.error("Failed to do service reassignment (will retry): " + dep.configuration().getName(), e);
AffinityTopologyVersion newTopVer = ctx.discovery().topologyVersionEx();
if (!newTopVer.equals(topVer)) {
assert newTopVer.compareTo(topVer) > 0;
// Reassignment will happen from topology event.
return;
}
ctx.timeout().addTimeoutObject(new GridTimeoutObject() {
private IgniteUuid id = IgniteUuid.randomUuid();
private long start = System.currentTimeMillis();
@Override public IgniteUuid timeoutId() {
return id;
}
@Override public long endTime() {
return start + RETRY_TIMEOUT;
}
@Override public void onTimeout() {
if (!busyLock.enterBusy())
return;
try {
// Try again.
onDeployment(dep, topVer);
}
finally {
busyLock.leaveBusy();
}
}
});
}
}
/**
* Topology listener.
*/
private class TopologyListener implements DiscoveryEventListener {
/** {@inheritDoc} */
@Override public void onEvent(DiscoveryEvent evt, final DiscoCache discoCache) {
if (!busyLock.enterBusy())
return;
//Must check that threadpool was not shutdown.
if (depExe.isShutdown())
return;
try {
final AffinityTopologyVersion topVer;
if (evt instanceof DiscoveryCustomEvent) {
DiscoveryCustomMessage msg = ((DiscoveryCustomEvent)evt).customMessage();
if (msg instanceof CacheAffinityChangeMessage) {
if (!((CacheAffinityChangeMessage)msg).exchangeNeeded())
return;
}
else if (msg instanceof DynamicCacheChangeBatch) {
if (!((DynamicCacheChangeBatch)msg).exchangeNeeded())
return;
}
else
return;
if (msg instanceof MetadataUpdateProposedMessage || msg instanceof MetadataUpdateAcceptedMessage)
return;
topVer = ((DiscoveryCustomEvent)evt).affinityTopologyVersion();
}
else
topVer = new AffinityTopologyVersion((evt).topologyVersion(), 0);
depExe.execute(new DepRunnable() {
@Override public void run0() {
// In case the cache instance isn't tracked by DiscoveryManager anymore.
discoCache.updateAlives(ctx.discovery());
ClusterNode oldest = discoCache.oldestAliveServerNodeWithCache();
if (oldest != null && oldest.isLocal()) {
final Collection<GridServiceDeployment> retries = new ConcurrentLinkedQueue<>();
if (ctx.deploy().enabled())
ctx.cache().context().deploy().ignoreOwnership(true);
try {
Iterator<Cache.Entry<Object, Object>> it = serviceEntries(
ServiceDeploymentPredicate.INSTANCE);
boolean firstTime = true;
while (it.hasNext()) {
Cache.Entry<Object, Object> e = it.next();
if (firstTime) {
markCompatibilityStateAsUsed();
firstTime = false;
}
GridServiceDeployment dep = (GridServiceDeployment)e.getValue();
try {
svcName.set(dep.configuration().getName());
ctx.cache().internalCache(UTILITY_CACHE_NAME).context().affinity().
affinityReadyFuture(topVer).get();
reassign(dep, topVer);
}
catch (IgniteCheckedException ex) {
if (!(e instanceof ClusterTopologyCheckedException))
LT.error(log, ex, "Failed to do service reassignment (will retry): " +
dep.configuration().getName());
retries.add(dep);
}
}
}
finally {
if (ctx.deploy().enabled())
ctx.cache().context().deploy().ignoreOwnership(false);
}
if (!retries.isEmpty())
onReassignmentFailed(topVer, retries);
}
Iterator<Cache.Entry<Object, Object>> it = serviceEntries(ServiceAssignmentsPredicate.INSTANCE);
// Clean up zombie assignments.
while (it.hasNext()) {
Cache.Entry<Object, Object> e = it.next();
if (cache.context().affinity().primaryByKey(ctx.grid().localNode(), e.getKey(), topVer)) {
String name = ((GridServiceAssignmentsKey)e.getKey()).name();
try {
if (cache.get(new GridServiceDeploymentKey(name)) == null) {
if (log.isDebugEnabled())
log.debug("Removed zombie assignments: " + e.getValue());
cache.getAndRemove(e.getKey());
}
}
catch (IgniteCheckedException ex) {
U.error(log, "Failed to clean up zombie assignments for service: " + name, ex);
}
}
}
}
});
}
finally {
busyLock.leaveBusy();
}
}
/**
* Handler for reassignment failures.
*
* @param topVer Topology version.
* @param retries Retries.
*/
private void onReassignmentFailed(final AffinityTopologyVersion topVer,
final Collection<GridServiceDeployment> retries) {
if (!busyLock.enterBusy())
return;
try {
// If topology changed again, let next event handle it.
if (ctx.discovery().topologyVersionEx().equals(topVer))
return;
for (Iterator<GridServiceDeployment> it = retries.iterator(); it.hasNext(); ) {
GridServiceDeployment dep = it.next();
try {
svcName.set(dep.configuration().getName());
reassign(dep, topVer);
it.remove();
}
catch (IgniteCheckedException e) {
if (!(e instanceof ClusterTopologyCheckedException))
LT.error(log, e, "Failed to do service reassignment (will retry): " +
dep.configuration().getName());
}
}
if (!retries.isEmpty()) {
ctx.timeout().addTimeoutObject(new GridTimeoutObject() {
private IgniteUuid id = IgniteUuid.randomUuid();
private long start = System.currentTimeMillis();
@Override public IgniteUuid timeoutId() {
return id;
}
@Override public long endTime() {
return start + RETRY_TIMEOUT;
}
@Override public void onTimeout() {
onReassignmentFailed(topVer, retries);
}
});
}
}
finally {
busyLock.leaveBusy();
}
}
}
/**
* @param e Entry.
*/
private void processAssignment(CacheEntryEvent<GridServiceAssignmentsKey, GridServiceAssignments> e) {
GridServiceAssignments assigns;
try {
assigns = e.getValue();
}
catch (IgniteException ex) {
if (X.hasCause(ex, ClassNotFoundException.class))
return;
else
throw ex;
}
if (assigns != null) {
svcName.set(assigns.name());
Throwable t = null;
try {
redeploy(assigns);
}
catch (Error | RuntimeException th) {
t = th;
}
GridServiceDeploymentFuture fut = depFuts.get(assigns.name());
if (fut != null && fut.configuration().equalsIgnoreNodeFilter(assigns.configuration())) {
depFuts.remove(assigns.name(), fut);
// Complete deployment futures once the assignments have been stored in cache.
fut.onDone(null, t);
}
}
// Handle undeployment.
else
undeploy(e.getKey().name());
}
/**
* @param name Name.
*/
private void undeploy(String name) {
svcName.set(name);
Collection<ServiceContextImpl> ctxs;
synchronized (locSvcs) {
ctxs = locSvcs.remove(name);
}
if (ctxs != null) {
synchronized (ctxs) {
cancel(ctxs, ctxs.size());
}
}
}
/**
*
*/
private abstract class DepRunnable implements Runnable {
/** {@inheritDoc} */
@Override public void run() {
if (!busyLock.enterBusy())
return;
// Won't block ServiceProcessor stopping process.
busyLock.leaveBusy();
svcName.set(null);
try {
run0();
}
catch (Throwable t) {
log.error("Error when executing service: " + svcName.get(), t);
if (t instanceof Error)
throw t;
}
finally {
svcName.set(null);
}
}
/**
* Abstract run method protected by busy lock.
*/
public abstract void run0();
}
/**
*
*/
static class ServiceDeploymentPredicate implements IgniteBiPredicate<Object, Object> {
/** */
static final ServiceDeploymentPredicate INSTANCE = new ServiceDeploymentPredicate();
/** */
private static final long serialVersionUID = 0L;
/** {@inheritDoc} */
@Override public boolean apply(Object key, Object val) {
return key instanceof GridServiceDeploymentKey;
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(ServiceDeploymentPredicate.class, this);
}
}
/**
*
*/
static class ServiceAssignmentsPredicate implements IgniteBiPredicate<Object, Object> {
/** */
static final ServiceAssignmentsPredicate INSTANCE = new ServiceAssignmentsPredicate();
/** */
private static final long serialVersionUID = 0L;
/** {@inheritDoc} */
@Override public boolean apply(Object key, Object val) {
return key instanceof GridServiceAssignmentsKey;
}
/** {@inheritDoc} */
@Override public String toString() {
return S.toString(ServiceAssignmentsPredicate.class, this);
}
}
/**
*/
@GridInternal
@SerializableTransient(methodName = "serializableTransient")
private static class ServiceTopologyCallable implements IgniteCallable<Map<UUID, Integer>> {
/** */
private static final long serialVersionUID = 0L;
/** */
private final String svcName;
/** */
private transient boolean waitedCacheInit;
/** */
@IgniteInstanceResource
private IgniteEx ignite;
/** */
@JobContextResource
private transient ComputeJobContext jCtx;
/** */
@LoggerResource
private transient IgniteLogger log;
/**
* @param svcName Service name.
*/
public ServiceTopologyCallable(String svcName) {
this.svcName = svcName;
}
/** {@inheritDoc} */
@Override public Map<UUID, Integer> call() throws Exception {
IgniteInternalCache<Object, Object> cache = ignite.context().cache().utilityCache();
if (cache == null) {
List<ComputeJobContext> pendingCtxs = ignite.context().service().pendingJobCtxs;
synchronized (pendingCtxs) {
// Double check cache reference after lock acqusition.
cache = ignite.context().cache().utilityCache();
if (cache == null) {
if (!waitedCacheInit) {
log.debug("Utility cache hasn't been initialized yet. Waiting.");
// waiting for a minute for cache initialization.
jCtx.holdcc(60 * 1000);
pendingCtxs.add(jCtx);
waitedCacheInit = true;
return null;
}
else {
log.error("Failed to gather service topology. Utility " +
"cache initialization is stuck.");
throw new IgniteCheckedException("Failed to gather service topology. Utility " +
"cache initialization is stuck.");
}
}
}
}
return serviceTopology(cache, svcName);
}
}
/**
*
*/
private static class ServicesCompatibilityState {
/** */
private final boolean srvcCompatibility;
/** */
private final boolean used;
/**
* @param srvcCompatibility Services compatibility mode ({@code true} if compatible with old nodes).
* @param used Services has been used.
*/
ServicesCompatibilityState(boolean srvcCompatibility, boolean used) {
this.srvcCompatibility = srvcCompatibility;
this.used = used;
}
}
}