/* * 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.geode.redis.internal.executor.list; import java.util.List; import org.apache.geode.cache.Region; import org.apache.geode.cache.query.QueryService; import org.apache.geode.internal.cache.GemFireCacheImpl; import org.apache.geode.redis.internal.ByteArrayWrapper; import org.apache.geode.redis.internal.ExecutionHandlerContext; import org.apache.geode.redis.internal.RedisDataType; import org.apache.geode.redis.internal.executor.AbstractExecutor; public abstract class ListExecutor extends AbstractExecutor { protected static final int LIST_EMPTY_SIZE = 2; protected static enum ListDirection { LEFT, RIGHT }; protected final static QueryService getQueryService() { return GemFireCacheImpl.getInstance().getQueryService(); } @SuppressWarnings("unchecked") @Override protected Region<Integer, ByteArrayWrapper> getOrCreateRegion(ExecutionHandlerContext context, ByteArrayWrapper key, RedisDataType type) { return (Region<Integer, ByteArrayWrapper>) context.getRegionProvider().getOrCreateRegion(key, type, context); } @SuppressWarnings("unchecked") protected Region<Integer, ByteArrayWrapper> getRegion(ExecutionHandlerContext context, ByteArrayWrapper key) { return (Region<Integer, ByteArrayWrapper>) context.getRegionProvider().getRegion(key); } /** * Helper method to be used by the push commands to push elements onto a list. Because our current * setup requires non trivial code to push elements in to a Region, I wanted all the push code to * reside in one place. * * @param key Name of the list * @param commandElems Pieces of the command, this is where the elements that need to be pushed * live * @param startIndex The index to start with in the commandElems list, inclusive * @param endIndex The index to end with in the commandElems list, exclusive * @param keyRegion Region of list * @param pushType ListDirection.LEFT || ListDirection.RIGHT * @param context Context of this push */ protected void pushElements(ByteArrayWrapper key, List<byte[]> commandElems, int startIndex, int endIndex, Region keyRegion, ListDirection pushType, ExecutionHandlerContext context) { String indexKey = pushType == ListDirection.LEFT ? "head" : "tail"; String oppositeKey = pushType == ListDirection.RIGHT ? "head" : "tail"; Integer index = (Integer) keyRegion.get(indexKey); Integer opp = (Integer) keyRegion.get(oppositeKey); if (index != opp) index += pushType == ListDirection.LEFT ? -1 : 1; // Subtract index if left push, add if right // push /** * Multi push command * * For every element that needs to be added */ for (int i = startIndex; i < endIndex; i++) { byte[] value = commandElems.get(i); ByteArrayWrapper wrapper = new ByteArrayWrapper(value); /** * * First, use the start index to attempt to insert the value into the Region * */ Object oldValue; do { oldValue = keyRegion.putIfAbsent(index, wrapper); if (oldValue != null) { index += pushType == ListDirection.LEFT ? -1 : 1; // Subtract index if left push, add if // right push } } while (oldValue != null); /** * * Next, update the index in the meta data region. Keep trying to replace the existing index * unless the index is further out than previously inserted, that's ok. Example below: * * ********************** LPUSH/LPUSH *************************** Push occurring at the same * time, further index update first | This push | | | | V V [-4] [-3] [-2] [-1] [0] [1] [2] * * In this case, -4 would already exist in the meta data region, therefore we do not try to * put -3 in the meta data region because a further index is already there. * *************************************************************** * * Another example * * ********************** LPUSH/LPOP ***************************** This push | Simultaneous * LPOP, meta data head index already updated to -2 | | | | V V [-4] [X] [-2] [-1] [0] [1] [2] * * In this case, -2 would already exist in the meta data region, but we need to make sure the * element at -4 is visible to all other threads so we will attempt to change the index to -4 * as long as it is greater than -4 * *************************************************************** * */ boolean indexSet = false; do { Integer existingIndex = (Integer) keyRegion.get(indexKey); if ((pushType == ListDirection.RIGHT && existingIndex < index) || (pushType == ListDirection.LEFT && existingIndex > index)) indexSet = keyRegion.replace(indexKey, existingIndex, index); else break; } while (!indexSet); } } }