MemoryDaemon.java
/*
** Module : MemoryDaemon.java
** Abstract : remote access to low-level memory buffer functions
**
** Copyright (c) 2013-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description---------------------------------
** 001 GES 20130430 First version. The idea is to provide remote access from the server to the
** client-side low-level memory management functions. For security purposes,
** this class is intentionally package private.
** 002 ECF 20150824 Implemented cleanup processing to deallocate memory not previously freed
** explicitly. This will happen in response to a server-side context cleanup.
** 003 OM 20160705 Added reference counter.
** 004 GES 20180322 Added compiledWordSize().
** 005 GBB 20240826 Moving to package osresource. Making access to the class package-private.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util.osresource;
import java.util.*;
import com.goldencode.p2j.net.*;
import com.goldencode.p2j.util.*;
/**
* Provides remote access to the {@link MemoryManager} static functions.
*/
class MemoryDaemon
implements LowLevelBuffer
{
/** Token used to authenticate with the dispatcher when registering APIs. */
private static Object modToken = null;
/**
* All currently allocated addresses mapped to their usage counters. If the counter reaches 0
* the the memory should be freed.
*/
private Map<Long, Integer> allocatedAddresses = new HashMap<>();
/**
* Create an instance and export its API to the network.
*
* @param single
* {@code true} to startup within the server process which must bypass the shared
* infrastructure initialization. Use {@code false} for the normal client JVM startup.
*/
MemoryDaemon(boolean single)
{
synchronized (MemoryDaemon.class)
{
modToken = RemoteObject.registerServer(LowLevelBuffer.class, this, modToken, single);
}
}
/**
* Reports on the native byte-ordering of the platform on which this library is
* running.
*
* @return {@code true} if the platform uses little-endian byte-ordering, {@code false} if
* the platform is big-endian.
*/
@Override
public boolean isLittleEndian()
{
return MemoryManager.isLittleEndian();
}
/**
* Returns the word size (the size of a pointer) for the native code as compiled. This may be
* different than the word size of the operating system in the case where 32-bit code is run
* on a 64-bit system.
*
* @return This value will be either 32 or 64 depending on how the native library was
* compiled.
*/
public int compiledWordSize()
{
return MemoryManager.compiledWordSize();
}
/**
* Allocate the given number of bytes as a new memory block and return the 64-bit address. All
* bytes in the memory region will be initialized to 0x00.
* <p>
* To duplicate the permissiveness of the 4GL memptr allocation (where certain operations often
* can read or write past the end of the allocated buffer without an access violation), the
* buffer allocated by this function will always be 16 bytes larger than the requested size.
*
* @param len
* The size in bytes of the memory region to allocate.
*
* @return The address of the allocated memory region or 0 if memory could not be allocated.
*/
@Override
public long allocate(long len)
{
long addr = MemoryManager.allocate(len);
if (addr != 0)
{
allocatedAddresses.put(addr, 1);
}
return addr;
}
/**
* De-allocate the memory block at the given 64-bit address. If this method returns
* {@code true}, the memory in this region MUST NOT be accessed after this call.
*
* @param addr
* The 64-bit pointer to a memory region previously allocated using this module.
* DO NOT pass in an invalid pointer. DO NOT deallocate the same pointer after this
* method returned {@code true}.
*
* @return {@code true} if the memory was deallocated and {@code false} if the deallocation
* failed because this memory zone was shared with other variable. If so then the
* counter was decremented and the memory will be released when it reaches zero.
*/
@Override
public boolean deallocate(long addr)
{
Integer counter = allocatedAddresses.get(addr);
if (counter == null)
{
// not a valid memory address
return false;
}
if (counter > 1)
{
// deallocation cannot be done there are other references to this memory. Decrement the
// counter and notify the caller that the memory is not free.
allocatedAddresses.put(addr, counter - 1);
return false;
}
deallocateImpl(addr, false);
return true;
}
/**
* Increments the number of references to this memory. When a variable is deallocated, just
* call {@link #deallocate(long)}. It will automatically decrement the counter and truly
* deallocate the memory when it reaches 0.
*
* @param addr
* The 64-bit pointer to a memory region previously allocated using this module.
* DO NOT pass in an invalid pointer.
*/
@Override
public void incrementReference(long addr)
{
Integer counter = allocatedAddresses.get(addr);
if (counter != null)
{
allocatedAddresses.put(addr, counter + 1);
}
}
/**
* Returns the next index position into the array which is the {@code null} byte, starting
* at the given index position.
*
* @param addr
* The 64-bit pointer to a memory region previously allocated using this module.
* DO NOT pass in an invalid pointer.
* @param idx
* The index position at which to start the search.
* @param size
* The length of the buffer. If the search reaches this byte without finding a
* null byte, then this value will be returned. If this is given as -1, then the
* caller has no size information and we operate in unsafe mode. In this mode
* the code will read until it finds a {@code null} or gets an access violation.
* This is how the 4GL does it too.
*
* @return The index of the next byte position which contains the {@code null} byte or the
* length of the array if no {@code null} byte is contained.
*/
@Override
public long findNextNull(long addr, long idx, long size)
{
return MemoryManager.findNextNull(addr, idx, size);
}
/**
* Read the specified range of contents from the memory block at the given 64-bit address.
*
* @param addr
* The 64-bit pointer to a memory region previously allocated using this module.
* DO NOT pass in an invalid pointer.
* @param offset
* The 0-based index position from the beginning of the buffer, from which to start
* reading.
* @param len
* The number of bytes to read. Must be greater than 0 and must NOT extend past the
* end of the buffer.
*
* @return The range of bytes read or {@code null} if there is any problem allocating
* the array.
*/
@Override
public byte[] read(long addr, long offset, long len)
{
return MemoryManager.read(addr, offset, len);
}
/**
* Write the specified contents to the memory block at the given 64-bit address plus offset.
*
* @param addr
* The 64-bit pointer to a memory region previously allocated using this module.
* DO NOT pass in an invalid pointer.
* @param offset
* The 0-based index position from the beginning of the buffer, at which to start
* writing.
* @param data
* The bytes to write. The entire contents of the array will be written. The length
* of the array must not be bigger than the number of bytes between the offset and
* the end of the buffer.
*/
@Override
public void write(long addr, long offset, byte[] data)
{
MemoryManager.write(addr, offset, data);
}
/**
* Copy the specified range of contents from the source memory block to the destination memory
* block.
*
* @param source
* The 64-bit pointer to a memory region previously allocated using this module.
* DO NOT pass in an invalid pointer.
* @param dest
* The 64-bit pointer to a memory region previously allocated using this module.
* DO NOT pass in an invalid pointer.
* @param offset
* The 0-based index position from the beginning of the buffer, at which to start
* writing.
* @param len
* The number of bytes to write. Must be greater than 0 and must NOT extend past the
* end of the destination buffer.
*/
@Override
public void copy(long source, long dest, long offset, long len)
{
MemoryManager.copy(source, dest, offset, len);
}
/**
* Deallocate all memory allocations which have not been deallocated explicitly previously.
*/
@Override
public void cleanup()
{
for (Long addr : allocatedAddresses.keySet())
{
deallocateImpl(addr, true);
}
allocatedAddresses.clear();
}
/**
* De-allocate the memory block at the given 64-bit address. Memory in this region MUST NOT
* be accessed after this call.
*
* @param addr
* The 64-bit pointer to a memory region previously allocated using this module.
* DO NOT pass in an invalid pointer. DO NOT deallocate the same pointer more than
* once.
* @param cleanup
* {@code true} if called during cleanup processing; {@code false} if called for an
* explicit deallocation.
*/
private void deallocateImpl(long addr, boolean cleanup)
{
MemoryManager.deallocate(addr);
if (!cleanup)
{
allocatedAddresses.remove(addr);
}
}
}