All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.daos.obj.IOSimpleDataDesc Maven / Gradle / Ivy

There is a newer version: 2.4.1
Show newest version
/*
 * (C) Copyright 2018-2021 Intel Corporation.
 *
 * SPDX-License-Identifier: BSD-2-Clause-Patent
 */

package io.daos.obj;

import io.daos.BufferAllocator;
import io.daos.Constants;
import io.daos.DaosEventQueue;
import io.daos.DaosIOException;
import io.netty.buffer.ByteBuf;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UnsupportedEncodingException;

/**
 * IO simple description for fetching and updating object records on given dkey. The differences between this class and
 * {@link IODataDescSync} are,
 * - this class defaults record size as 1, iod type as ARRAY.
 * - this class is always reusable.
 * - this class support asynchronous update/fetch.
 *
 * Each record is described in {@link SimpleEntry}.
 * To make JNI call efficient and avoid memory fragmentation, the dkey and entries are serialized to direct buffers
 * which then de-serialized in native code.
 *
 * 

* There are two types of buffers, Description Buffer and Data Buffers. The Description Buffer holds entries * description, like their akey, type, size. The Data Buffers of entries holds actual data for either update or fetch. * {@link #release()} method should be called after object update or fetch. For update, user is responsible for * releasing data buffers. For fetch, user can determine who release fetch buffers. * See {@link SimpleEntry#release(boolean)}. *

*/ public class IOSimpleDataDesc extends IODataDescBase implements DaosEventQueue.Attachment { private boolean dkeyChanged; private final int maxKenLen; private int dkeyLen; private int nbrOfAkeysToRequest; private final boolean async; private boolean released; private DaosEventQueue.Event event; private final long eqHandle; private int retCode = Integer.MAX_VALUE; public static final int RET_CODE_SUCCEEDED = Constants.RET_CODE_SUCCEEDED; private static final Logger log = LoggerFactory.getLogger(IOSimpleDataDesc.class); /** * Create simple description for synchronous or asynchronous update/fetch depending on * if eqWrapperHandle has zero value. * * @param maxKeyStrLen * max key len in str * @param nbrOfEntries * number of akey entries * @param entryBufLen * buffer length of each entry * @param eqWrapperHandle * 0L for synchronous. asynchronous otherwise. */ protected IOSimpleDataDesc(int maxKeyStrLen, int nbrOfEntries, int entryBufLen, long eqWrapperHandle) { super(null, true); if (maxKeyStrLen > Short.MAX_VALUE/2 || maxKeyStrLen <= 0) { throw new IllegalArgumentException("number of entries should be positive and no larger than " + Short.MAX_VALUE/2 + ". " + maxKeyStrLen); } this.maxKenLen = maxKeyStrLen * 2; // 2 bytes per string character if (nbrOfEntries > Short.MAX_VALUE || nbrOfEntries < 0) { throw new IllegalArgumentException("number of entries should be positive and no larger than " + Short.MAX_VALUE + ". " + nbrOfEntries); } this.eqHandle = eqWrapperHandle; this.async = eqWrapperHandle != 0; // 8 for storing native desc pointer // 2 for storing maxKenLen // 2 for number of entries // 2 for dkey length // 2 for actual number of entries starting from first entry having data int tmpLen = (16 + maxKenLen); // 8 for native EQ pointer // 2 for event ID tmpLen += async ? 10 : 0; for (int i = 0; i < nbrOfEntries; i++) { SimpleEntry entry = new SimpleEntry(entryBufLen); akeyEntries.add(entry); tmpLen += entry.getDescLen(); } totalRequestBufLen = tmpLen; // for rc and returned actual size tmpLen += 4 + nbrOfEntries * Constants.ENCODED_LENGTH_EXTENT; totalDescBufferLen = tmpLen; this.descBuffer = BufferAllocator.objBufWithNativeOrder(totalDescBufferLen); prepareNativeDesc(); if (!async) { // native desc handle written to the start of descBuffer in native code. DaosObjClient.allocateSimpleDesc(descBuffer.memoryAddress(), false); checkNativeDesc(); } // group managed native desc for asynchronous desc } private void checkLen(int len, String keyType) { if (len > maxKenLen) { throw new IllegalArgumentException(keyType + " length should not exceed " + maxKenLen/2 + ". " + len/2); } } protected void checkNativeDesc() { descBuffer.readerIndex(0); if (descBuffer.readLong() == 0L) { throw new IllegalStateException("no native desc created"); } } @Override public void setEvent(DaosEventQueue.Event event) { this.event = event; event.setAttachment(this); } public void setUpdateOrFetch(boolean updateOrFetch) { this.updateOrFetch = updateOrFetch; } public boolean isUpdateOrFetch() { return updateOrFetch; } public int getNbrOfAkeysToRequest() { return nbrOfAkeysToRequest; } public boolean isAsync() { return async; } @Override public void setDkey(String dkey) { this.dkey = dkey; this.dkeyLen = dkey.length() * 2; checkLen(dkeyLen, "dkey"); dkeyChanged = true; } /** * encode dkey + entries descriptions to the Description Buffer. * encode entries data to Data Buffer. */ @Override public void encode() { if (encoded) { return; } if (nbrOfAkeysToRequest == 0) { throw new IllegalArgumentException("at least one of entries should have data"); } if (resultParsed) { throw new IllegalStateException("reuse() method is not called"); } descBuffer.readerIndex(0); descBuffer.writerIndex(12); if (async) { // assuming same event queue descBuffer.writerIndex(descBuffer.writerIndex() + 8); descBuffer.writeShort(event.getId()); } if (dkeyChanged) { descBuffer.writeShort(dkeyLen); writeKey(dkey); } else { descBuffer.writerIndex(descBuffer.writerIndex() + 2 + maxKenLen); } descBuffer.writeShort(nbrOfAkeysToRequest); int count = 0; for (Entry entry : akeyEntries) { if (!((SimpleEntry)entry).isReused()) { break; } entry.encode(); count++; } if (nbrOfAkeysToRequest > count) { throw new IllegalStateException("number of akeys to request " + nbrOfAkeysToRequest + ", should not exceed " + "total reused entries, " + count); } encoded = true; } private void prepareNativeDesc() { // skip native handle descBuffer.writeLong(0L); descBuffer.writeShort(maxKenLen); descBuffer.writeShort(akeyEntries.size()); if (async) { // for asynchronous descBuffer.writeLong(eqHandle); // skip event id descBuffer.writerIndex(descBuffer.writerIndex() + 2); } // skip dkeylen, dkey and nbr of requests descBuffer.writerIndex(descBuffer.writerIndex() + 2 + maxKenLen + 2); for (Entry entry : akeyEntries) { ((SimpleEntry)entry).putAddress(descBuffer); } } private void writeKey(String dkey) { int pos = descBuffer.writerIndex(); for (int i = 0; i < dkey.length(); i++) { descBuffer.writeShort(dkey.charAt(i)); } descBuffer.writerIndex(pos + maxKenLen); } /** * if the object update or fetch succeeded. * * @return true or false */ @Override public boolean isSucceeded() { return retCode == RET_CODE_SUCCEEDED; } @Override public IODataDesc duplicate() throws IOException { throw new UnsupportedOperationException("duplicate is not supported"); } @Override public ByteBuf getDescBuffer() { return descBuffer; } @Override public SimpleEntry getEntry(int index) { return (SimpleEntry) akeyEntries.get(index); } public int getRetCode() { return retCode; } protected void setCause(Throwable de) { cause = de; } protected void parseUpdateResult() { if (async) { descBuffer.writerIndex(descBuffer.capacity()); descBuffer.readerIndex(totalRequestBufLen); retCode = descBuffer.readInt(); } else { retCode = RET_CODE_SUCCEEDED; } resultParsed = true; } /** * parse result after JNI call. */ protected void parseFetchResult() { if (!updateOrFetch) { if (resultParsed) { return; } int nbrOfReq = nbrOfAkeysToRequest; int count = 0; // update actual size int idx = totalRequestBufLen; descBuffer.writerIndex(descBuffer.capacity()); descBuffer.readerIndex(idx); retCode = descBuffer.readInt(); if (retCode != RET_CODE_SUCCEEDED) { resultParsed = true; return; } idx += 4; for (Entry entry : akeyEntries) { if (count < nbrOfReq) { descBuffer.readerIndex(idx); entry.setActualSize(descBuffer.readInt()); ByteBuf dataBuffer = entry.dataBuffer; dataBuffer.writerIndex(dataBuffer.readerIndex() + entry.actualSize); idx += Constants.ENCODED_LENGTH_EXTENT; continue; } break; } resultParsed = true; return; } throw new UnsupportedOperationException("only support for fetch"); } /** * should be called before setting keys and entries except it's first time use. */ @Override public void reuse() { this.resultParsed = false; this.nbrOfAkeysToRequest = 0; this.totalRequestSize = 0; this.dkeyChanged = false; this.encoded = false; this.retCode = Integer.MAX_VALUE; for (Entry e : akeyEntries) { e.actualSize = 0; ((SimpleEntry)e).reused = false; ((SimpleEntry)e).akeyChanged = false; } } @Override public boolean isReusable() { return true; } @Override public void ready() { if (isUpdateOrFetch()) { parseUpdateResult(); } else { parseFetchResult(); } } @Override public boolean alwaysBoundToEvt() { return async; } /** * release all buffers created from this object and its entry objects. Be noted, the fetch data buffers are * released too if this desc is for fetch. If you don't want release them too early, please call * {@link #release(boolean)} with false as parameter. */ @Override public void release() { release(true); } /** * same as {@link #release()}, but give user a choice whether release fetch buffers or not. * * @param releaseFetchBuffer * true to release all fetch buffers, false otherwise. */ public void release(boolean releaseFetchBuffer) { if (!released) { if (!async) { descBuffer.readerIndex(0); descBuffer.writerIndex(descBuffer.capacity()); long nativeDescPtr = descBuffer.readLong(); if (hasNativeDec(nativeDescPtr)) { DaosObjClient.releaseDescSimple(nativeDescPtr); } } // otherwise, native desc will be released in SimpleDataDescGrp this.descBuffer.release(); if ((!resultParsed) && event != null) { try { event.abort(); } catch (DaosIOException e) { log.error("failed to abort event bound to " + this, e); } event = null; } this.released = true; } if (updateOrFetch || releaseFetchBuffer) { for (Entry entry : akeyEntries) { entry.releaseDataBuffer(); } } } private boolean hasNativeDec(long nativeDescPtr) { return nativeDescPtr != 0L; } @Override public String toString() { return toString(2048); } public String toString(int maxSize) { StringBuilder sb = new StringBuilder(); sb.append("dkey: ").append(dkey).append(", akey entries\n"); int nbr = 0; for (Entry e : akeyEntries) { sb.append("[").append(e.toString()).append("]"); nbr++; if (sb.length() < maxSize) { sb.append(','); } else { break; } } if (nbr < akeyEntries.size()) { sb.append("..."); } return sb.toString(); } /** * A entry to describe record update or fetch on given akey. For array, each entry object represents consecutive * records of given key. Multiple entries should be created for non-consecutive records of given key. */ public class SimpleEntry extends BaseEntry { private boolean akeyChanged; private int akeyLen; private boolean reused; /** * construction for reusable entry. * * @param bufferLen * @throws IOException */ protected SimpleEntry(int bufferLen) { this.dataBuffer = BufferAllocator.objBufWithNativeOrder(bufferLen); } /** * length of this entry when encoded into the Description Buffer. * * @return length */ @Override public int getDescLen() { // 22 = dkey len(2) + recx idx(8) + recx nr(4) + data buffer mem address(8) return 22 + maxKenLen; } public boolean isReused() { return reused; } /** * this method should be called before reusing the data buffer. * The data buffer will be cleared before returning to user. * * @return reused original buffer */ public ByteBuf reuseBuffer() { this.dataBuffer.clear(); return this.dataBuffer; } /** * set Akey and its info for update. * User should call {@link #reuseBuffer()} before calling this method. * * @param akey * null for reusing existing akey. * @param offset * @param buf * reused data buffer * @throws UnsupportedEncodingException */ public void setEntryForUpdate(String akey, long offset, ByteBuf buf) { if (buf.readerIndex() != 0) { throw new IllegalArgumentException("buffer's reader index should be 0. " + buf.readerIndex()); } setEntry(akey, offset, buf, 0); } /** * set Akey and its info for fetch. * {@link #reuseBuffer()} is not necessary to be called since it'll be called automatically inside * this method. * * @param akey * null for reusing existing akey. * @param offset * @param fetchDataSize * @throws UnsupportedEncodingException */ public void setEntryForFetch(String akey, long offset, int fetchDataSize) { this.dataBuffer.clear(); setEntry(akey, offset, this.dataBuffer, fetchDataSize); } private void setEntry(String akey, long offset, ByteBuf buf, int fetchDataSize) { if (akey != null) { setAkey(akey); } this.offset = offset; if (updateOrFetch) { this.dataSize = buf.readableBytes(); } else { this.dataSize = fetchDataSize; if (dataSize > buf.capacity()) { throw new IllegalArgumentException("data size, " + dataSize + "should not exceed buffer capacity, " + buf.capacity()); } } if (dataSize <= 0) { throw new IllegalArgumentException("data size should be positive, " + dataSize); } if (buf != dataBuffer) { throw new IllegalArgumentException("buffer mismatch"); } nbrOfAkeysToRequest++; totalRequestSize += dataSize; this.reused = true; } private void setAkey(String akey) { this.key = akey; this.akeyLen = akey.length() * 2; checkLen(akeyLen, "akey"); this.akeyChanged = true; } /** * encode entry to the description buffer which will be decoded in native code.
* */ @Override protected void encode() { if (akeyChanged) { descBuffer.writeShort(akeyLen); writeKey(key); } else { descBuffer.writerIndex(descBuffer.writerIndex() + 2 + maxKenLen); } descBuffer.writeLong(offset); descBuffer.writeInt(dataSize); // skip memory address descBuffer.writerIndex(descBuffer.writerIndex() + 8); } private void putAddress(ByteBuf descBuffer) { // skip akeylen, akey, offset and length descBuffer.writerIndex(descBuffer.writerIndex() + maxKenLen + 14); descBuffer.writeLong(dataBuffer.memoryAddress()); } public boolean isFetchBufReleased() { if (!updateOrFetch) { return dataBuffer == null; } throw new UnsupportedOperationException("only support for fetch, akey: " + key); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(updateOrFetch ? "update " : "fetch ").append("entry: "); sb.append(key).append('|') .append(offset).append('|') .append(dataSize); return sb.toString(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy