| /* |
| * 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.cache.query.index.sorted; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.UUID; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.IgniteException; |
| import org.apache.ignite.IgniteLogger; |
| import org.apache.ignite.internal.GridKernalContext; |
| import org.apache.ignite.internal.IgniteInternalFuture; |
| import org.apache.ignite.internal.cache.query.index.IndexName; |
| import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndexKeyType; |
| import org.apache.ignite.internal.cache.query.index.sorted.inline.InlineIndexTree; |
| import org.apache.ignite.internal.cache.query.index.sorted.keys.IndexKey; |
| import org.apache.ignite.internal.metric.IoStatisticsHolderIndex; |
| import org.apache.ignite.internal.pagemem.PageIdUtils; |
| import org.apache.ignite.internal.pagemem.PageMemory; |
| import org.apache.ignite.internal.processors.cache.CacheGroupContext; |
| import org.apache.ignite.internal.processors.cache.GridCacheContext; |
| import org.apache.ignite.internal.processors.cache.IgniteCacheOffheapManager; |
| import org.apache.ignite.internal.processors.cache.persistence.CacheDataRow; |
| import org.apache.ignite.internal.processors.cache.persistence.RootPage; |
| import org.apache.ignite.internal.processors.cache.persistence.metastorage.pendingtask.DurableBackgroundTask; |
| import org.apache.ignite.internal.processors.cache.persistence.metastorage.pendingtask.DurableBackgroundTaskResult; |
| import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree; |
| import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIoResolver; |
| import org.apache.ignite.internal.util.future.GridFutureAdapter; |
| import org.apache.ignite.internal.util.typedef.internal.CU; |
| import org.apache.ignite.internal.util.typedef.internal.S; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.internal.util.worker.GridWorker; |
| import org.apache.ignite.thread.IgniteThread; |
| import org.jetbrains.annotations.Nullable; |
| |
| import static java.util.Collections.singleton; |
| import static org.apache.ignite.internal.metric.IoStatisticsType.SORTED_INDEX; |
| |
| /** |
| * Tasks that cleans up index tree. |
| */ |
| public class DurableBackgroundCleanupIndexTreeTask implements DurableBackgroundTask { |
| /** */ |
| private static final long serialVersionUID = 0L; |
| |
| /** */ |
| private List<Long> rootPages; |
| |
| /** */ |
| private transient volatile List<InlineIndexTree> trees; |
| |
| /** */ |
| private String cacheGrpName; |
| |
| /** */ |
| private final String cacheName; |
| |
| /** */ |
| private String schemaName; |
| |
| /** */ |
| private final String treeName; |
| |
| /** */ |
| private final String idxName; |
| |
| /** */ |
| private final String id; |
| |
| /** Logger. */ |
| @Nullable private transient volatile IgniteLogger log; |
| |
| /** Worker tasks. */ |
| @Nullable private transient volatile GridWorker worker; |
| |
| /** */ |
| public DurableBackgroundCleanupIndexTreeTask( |
| List<Long> rootPages, |
| List<InlineIndexTree> trees, |
| String cacheGrpName, |
| String cacheName, |
| IndexName idxName, |
| String treeName |
| ) { |
| this.rootPages = rootPages; |
| this.trees = trees; |
| this.cacheGrpName = cacheGrpName; |
| this.cacheName = cacheName; |
| this.id = UUID.randomUUID().toString(); |
| this.idxName = idxName.idxName(); |
| this.schemaName = idxName.schemaName(); |
| this.treeName = treeName; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String name() { |
| return "DROP_SQL_INDEX-" + schemaName + "." + idxName + "-" + id; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public IgniteInternalFuture<DurableBackgroundTaskResult> executeAsync(GridKernalContext ctx) { |
| log = ctx.log(this.getClass()); |
| |
| assert worker == null; |
| |
| GridFutureAdapter<DurableBackgroundTaskResult> fut = new GridFutureAdapter<>(); |
| |
| worker = new GridWorker( |
| ctx.igniteInstanceName(), |
| "async-durable-background-task-executor-" + name(), |
| log |
| ) { |
| /** {@inheritDoc} */ |
| @Override protected void body() { |
| try { |
| execute(ctx); |
| |
| worker = null; |
| |
| fut.onDone(DurableBackgroundTaskResult.complete(null)); |
| } |
| catch (Throwable t) { |
| worker = null; |
| |
| fut.onDone(DurableBackgroundTaskResult.restart(t)); |
| } |
| } |
| }; |
| |
| new IgniteThread(worker).start(); |
| |
| return fut; |
| } |
| |
| /** |
| * Task execution. |
| * |
| * @param ctx Kernal context. |
| */ |
| private void execute(GridKernalContext ctx) { |
| List<InlineIndexTree> trees0 = trees; |
| |
| if (trees0 == null) { |
| trees0 = new ArrayList<>(rootPages.size()); |
| |
| GridCacheContext cctx = ctx.cache().context().cacheContext(CU.cacheId(cacheName)); |
| |
| int grpId = CU.cacheGroupId(cacheName, cacheGrpName); |
| |
| CacheGroupContext grpCtx = ctx.cache().cacheGroup(grpId); |
| |
| // If group context is null, it means that group doesn't exist and we don't need this task anymore. |
| if (grpCtx == null) |
| return; |
| |
| IgniteCacheOffheapManager offheap = grpCtx.offheap(); |
| |
| if (treeName != null) { |
| ctx.cache().context().database().checkpointReadLock(); |
| |
| try { |
| int cacheId = CU.cacheId(cacheName); |
| |
| for (int segment = 0; segment < rootPages.size(); segment++) { |
| try { |
| RootPage rootPage = offheap.findRootPageForIndex(cacheId, treeName, segment); |
| |
| if (rootPage != null && rootPages.get(segment) == rootPage.pageId().pageId()) |
| offheap.dropRootPageForIndex(cacheId, treeName, segment); |
| } |
| catch (IgniteCheckedException e) { |
| throw new IgniteException(e); |
| } |
| } |
| } |
| finally { |
| ctx.cache().context().database().checkpointReadUnlock(); |
| } |
| } |
| |
| IoStatisticsHolderIndex stats = new IoStatisticsHolderIndex( |
| SORTED_INDEX, |
| cctx.name(), |
| idxName, |
| cctx.kernalContext().metric() |
| ); |
| |
| PageMemory pageMem = grpCtx.dataRegion().pageMemory(); |
| |
| for (int i = 0; i < rootPages.size(); i++) { |
| Long rootPage = rootPages.get(i); |
| |
| assert rootPage != null; |
| |
| if (skipDeletedRoot(grpId, pageMem, rootPage)) { |
| ctx.log(getClass()).warning(S.toString("Skipping deletion of the index tree", |
| "cacheGrpName", cacheGrpName, false, |
| "cacheName", cacheName, false, |
| "idxName", idxName, false, |
| "segment", i, false, |
| "rootPageId", PageIdUtils.toDetailString(rootPage), false |
| )); |
| |
| continue; |
| } |
| |
| // Below we create a fake index tree using it's root page, stubbing some parameters, |
| // because we just going to free memory pages that are occupied by tree structure. |
| try { |
| String treeName = "deletedTree_" + i + "_" + name(); |
| |
| InlineIndexTree tree = new InlineIndexTree( |
| null, cctx, treeName, cctx.offheap(), cctx.offheap().reuseListForIndex(treeName), |
| cctx.dataRegion().pageMemory(), PageIoResolver.DEFAULT_PAGE_IO_RESOLVER, |
| rootPage, false, 0, new IndexKeyTypeSettings(), null, |
| stats, new NoopRowHandlerFactory(), null); |
| |
| trees0.add(tree); |
| } |
| catch (IgniteCheckedException e) { |
| throw new IgniteException(e); |
| } |
| } |
| } |
| |
| ctx.cache().context().database().checkpointReadLock(); |
| |
| try { |
| for (int i = 0; i < trees0.size(); i++) { |
| BPlusTree tree = trees0.get(i); |
| |
| try { |
| tree.destroy(null, true); |
| } |
| catch (IgniteCheckedException e) { |
| throw new IgniteException(e); |
| } |
| } |
| } |
| finally { |
| ctx.cache().context().database().checkpointReadUnlock(); |
| } |
| } |
| |
| /** |
| * Checks that pageId is still relevant and has not been deleted / reused. |
| * @param grpId Cache group id. |
| * @param pageMem Page memory instance. |
| * @param rootPageId Root page identifier. |
| * @return {@code true} if root page was deleted/reused, {@code false} otherwise. |
| */ |
| private boolean skipDeletedRoot(int grpId, PageMemory pageMem, long rootPageId) { |
| try { |
| long page = pageMem.acquirePage(grpId, rootPageId); |
| |
| try { |
| long pageAddr = pageMem.readLock(grpId, rootPageId, page); |
| |
| try { |
| return pageAddr == 0; |
| } |
| finally { |
| if (pageAddr != 0) |
| pageMem.readUnlock(grpId, rootPageId, page); |
| } |
| } |
| finally { |
| pageMem.releasePage(grpId, rootPageId, page); |
| } |
| } |
| catch (IgniteCheckedException e) { |
| throw new IgniteException("Cannot acquire tree root page.", e); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void cancel() { |
| trees = null; |
| |
| GridWorker w = worker; |
| |
| if (w != null) { |
| worker = null; |
| |
| U.awaitForWorkersStop(singleton(w), true, log); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public String toString() { |
| return S.toString(DurableBackgroundCleanupIndexTreeTask.class, this); |
| } |
| |
| /** */ |
| private static class NoopRowHandlerFactory implements InlineIndexRowHandlerFactory { |
| /** {@inheritDoc} */ |
| @Override public InlineIndexRowHandler create(SortedIndexDefinition sdef, IndexKeyTypeSettings keyTypeSettings) { |
| return new InlineIndexRowHandler() { |
| /** {@inheritDoc} */ |
| @Override public IndexKey indexKey(int idx, CacheDataRow row) { |
| return null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public List<InlineIndexKeyType> inlineIndexKeyTypes() { |
| return Collections.emptyList(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public List<IndexKeyDefinition> indexKeyDefinitions() { |
| return Collections.emptyList(); |
| } |
| |
| @Override public IndexKeyTypeSettings indexKeyTypeSettings() { |
| return null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public int partition(CacheDataRow row) { |
| return 0; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public Object cacheKey(CacheDataRow row) { |
| return null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public Object cacheValue(CacheDataRow row) { |
| return null; |
| } |
| }; |
| } |
| } |
| } |