Skip to content

Commit

Permalink
21133: Adds optional constraints to entity creation, fixes some bugs …
Browse files Browse the repository at this point in the history
…and resource leaks around failed entity creations, MINOR (#212)
  • Loading branch information
howsohazard authored Aug 5, 2024
1 parent f9b8531 commit ebf48b2
Show file tree
Hide file tree
Showing 10 changed files with 301 additions and 158 deletions.
6 changes: 3 additions & 3 deletions docs/language.js
Original file line number Diff line number Diff line change
Expand Up @@ -1710,16 +1710,16 @@ var data = [
},

{
"parameter" : "call_entity id entity [string label_name] [assoc arguments] [number operation_limit] [number max_node_allocations] [number max_opcode_execution_depth]",
"parameter" : "call_entity id entity [string label_name] [assoc arguments] [number operation_limit] [number max_node_allocations] [number max_opcode_execution_depth] [number max_contained_entities] [number max_contained_entity_depth] [number max_entity_id_length]",
"output" : "*",
"permissions" : "e",
"new scope" : true,
"description" : "Calls the contained entity specified by id, using the entity as the new entity context. It will evaluate to the return value of the call, null if not found. If string is specified, then it will call the label specified by string. If assoc is specified, then it will pass assoc as the arguments on the scope stack. If operation_limit is specified, it represents the number of operations that are allowed to be performed. If operation_limit is 0 or infinite, then an infinite of operations will be allotted to the entity, but only if its containing entity (the current entity) has infinite operations. The root entity has infinite computing cycles. If max_node_allocations is specified, it represents the maximum number of nodes that are allowed to be allocated, limiting the total memory. If max_node_allocations is 0 or infinite, then there is no limit to the number of nodes to be allotted to the entity as long as the machine has sufficient memory, but only if the containing entity (the current entity) has unlimited memory access. If max_opcode_execution_depth is 0 or infinite and the caller also has no limit, then there is no limit to the depth that opcodes can execute, otherwise max_opcode_execution_depth limits how deep nested opcodes will be called. The execution performed will use a random number stream created from the entity's random number stream.",
"description" : "Calls the contained entity specified by id, using the entity as the new entity context. It will evaluate to the return value of the call, null if not found. If string is specified, then it will call the label specified by string. If assoc is specified, then it will pass assoc as the arguments on the scope stack. If operation_limit is specified, it represents the number of operations that are allowed to be performed. If operation_limit is 0 or infinite, then an infinite of operations will be allotted to the entity, but only if its containing entity (the current entity) has infinite operations. The root entity has infinite computing cycles. If max_node_allocations is specified, it represents the maximum number of nodes that are allowed to be allocated, limiting the total memory. If max_node_allocations is 0 or infinite, then there is no limit to the number of nodes to be allotted to the entity as long as the machine has sufficient memory, but only if the containing entity (the current entity) has unlimited memory access. If max_opcode_execution_depth is 0 or infinite and the caller also has no limit, then there is no limit to the depth that opcodes can execute, otherwise max_opcode_execution_depth limits how deep nested opcodes will be called. The parameters max_contained_entities, max_contained_entity_depth, and max_entity_id_length constrain what they describe, and are primarily useful when ensuring that an entity and all its contained entities can be stored out to the filesystem. The execution performed will use a random number stream created from the entity's random number stream.",
"example" : "(create_entities \"TestContainerExec\"\n (lambda (parallel\n ##d (print \"hello \" x)\n )) \n)\n\n(print (call_entity \"TestContainerExec\" \"d\" (assoc x \"goodbye\")))"
},

{
"parameter" : "call_entity_get_changes id entity [string label_name] [assoc arguments] [number operation_limit] [number max_node_allocations] [number max_opcode_execution_depth]",
"parameter" : "call_entity_get_changes id entity [string label_name] [assoc arguments] [number operation_limit] [number max_node_allocations] [number max_opcode_execution_depth] [number max_contained_entities] [number max_contained_entity_depth] [number max_entity_id_length]",
"output" : "list of *1 *2",
"permissions" : "e",
"new scope" : true,
Expand Down
19 changes: 19 additions & 0 deletions src/Amalgam/amlg_code/full_test.amlg
Original file line number Diff line number Diff line change
Expand Up @@ -3415,6 +3415,25 @@
(print (unparse (call_entity "TestContainerExec" "b") (true) (true)))
(print (unparse (call_entity "TestContainerExec" "c" (assoc x 5)) (true) (true)))

(print "execution limits tests\n")
(create_entities "ExecLimitsTest" (lambda
(null
#copy_entity
(while (true)
(clone_entities (null) (null))
)

#load
(while
(true)
)
))
)

(call_entity "ExecLimitsTest" "load" (null) 100)
(call_entity "ExecLimitsTest" "copy_entity" (null) 1000 1000 100 10 3 20)
(print (total_size (flatten_entity "ExecLimitsTest")) "\n")

(print "--call_entity_get_changes--\n")
(create_entities "CEGCTest" (lambda
(parallel ##a_assign
Expand Down
42 changes: 36 additions & 6 deletions src/Amalgam/entity/Entity.h
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,19 @@ class Entity
return 0;
}

//returns the total number of all contained entities including indirectly contained entities
inline size_t GetTotalNumContainedEntitiesIncludingSelf()
{
size_t total = 1;
if(hasContainedEntities)
{
for(Entity *e : entityRelationships.relationships->containedEntities)
total += e->GetTotalNumContainedEntitiesIncludingSelf();
}

return total;
}

//returns direct access to vector of pointers to Entity objects contained by this Entity
inline std::vector<Entity *> &GetContainedEntities()
{
Expand Down Expand Up @@ -480,16 +493,17 @@ class Entity
{
public:
inline EntityReferenceBufferReference()
: bufferReference(nullptr)
: maxEntityPathDepth(0), bufferReference(nullptr)
{ }

inline EntityReferenceBufferReference(std::vector<EntityReferenceType> &buffer)
: bufferReference(&buffer)
: maxEntityPathDepth(0), bufferReference(&buffer)
{ }

inline EntityReferenceBufferReference(EntityReferenceBufferReference &&erbr)
{
bufferReference = erbr.bufferReference;
maxEntityPathDepth = erbr.maxEntityPathDepth;
erbr.bufferReference = nullptr;
}

Expand All @@ -504,6 +518,7 @@ class Entity
{
bufferReference->clear();
bufferReference = nullptr;
maxEntityPathDepth = 0;
}
}

Expand All @@ -515,6 +530,7 @@ class Entity
bufferReference->clear();

bufferReference = erbr.bufferReference;
maxEntityPathDepth = erbr.maxEntityPathDepth;
erbr.bufferReference = nullptr;
}
return *this;
Expand All @@ -535,6 +551,9 @@ class Entity
return *bufferReference;
}

//maximum depth of an id path
size_t maxEntityPathDepth;

protected:
std::vector<EntityReferenceType> *bufferReference;
};
Expand All @@ -554,23 +573,31 @@ class Entity
else
erbr = EntityReferenceBufferReference(entityReadReferenceBuffer);

erbr.maxEntityPathDepth = 0;

if(include_this_entity)
{
if constexpr(std::is_same<EntityReferenceType, EntityWriteReference>::value)
entityWriteReferenceBuffer.emplace_back(this);
else
entityReadReferenceBuffer.emplace_back(this);

erbr.maxEntityPathDepth++;
}

GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse<EntityReferenceType>();
size_t max_depth = 0;
GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse<EntityReferenceType>(0, max_depth);
erbr.maxEntityPathDepth += max_depth;
return erbr;
}

//appends deply contained entity references to erbr
template<typename EntityReferenceType>
void AppendAllDeeplyContainedEntityReferencesGroupedByDepth(EntityReferenceBufferReference<EntityReferenceType> &erbr)
{
GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse<EntityReferenceType>();
size_t max_depth = 0;
GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse<EntityReferenceType>(0, max_depth);
erbr.maxEntityPathDepth += max_depth;
}

//gets the current state of the random stream in string form
Expand Down Expand Up @@ -736,8 +763,11 @@ class Entity

//helper function for GetAllDeeplyContainedEntityReadReferencesGroupedByDepth
template<typename EntityReferenceType>
bool GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse()
bool GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse(size_t cur_depth, size_t &max_depth)
{
if(cur_depth > max_depth)
max_depth = cur_depth;

if(!hasContainedEntities)
return true;

Expand All @@ -758,7 +788,7 @@ class Entity

for(auto &ce : contained_entities)
{
if(!ce->GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse<EntityReferenceType>())
if(!ce->GetAllDeeplyContainedEntityReferencesGroupedByDepthRecurse<EntityReferenceType>(cur_depth + 1, max_depth))
return false;
}

Expand Down
129 changes: 121 additions & 8 deletions src/Amalgam/interpreter/Interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,8 @@ EvaluableNode *Interpreter::RewriteByFunction(EvaluableNodeReference function, E
return result;
}

bool Interpreter::PopulatePerformanceConstraintsFromParams(std::vector<EvaluableNode *> &params, size_t perf_constraint_param_offset, PerformanceConstraints &perf_constraints)
bool Interpreter::PopulatePerformanceConstraintsFromParams(std::vector<EvaluableNode *> &params,
size_t perf_constraint_param_offset, PerformanceConstraints &perf_constraints, bool include_entity_constraints)
{
//start with constraints if there are already performance constraints
bool any_constraints = (performanceConstraints != nullptr);
Expand All @@ -792,10 +793,10 @@ bool Interpreter::PopulatePerformanceConstraintsFromParams(std::vector<Evaluable
//populate maxNumAllocatedNodes
perf_constraints.curNumAllocatedNodesAllocatedToEntities = 0;
perf_constraints.maxNumAllocatedNodes = 0;
size_t nodes_allowed_offset = perf_constraint_param_offset + 1;
if(params.size() > nodes_allowed_offset)
size_t max_num_allocated_nodes_offset = perf_constraint_param_offset + 1;
if(params.size() > max_num_allocated_nodes_offset)
{
double value = InterpretNodeIntoNumberValue(params[nodes_allowed_offset]);
double value = InterpretNodeIntoNumberValue(params[max_num_allocated_nodes_offset]);
//nan will fail, so don't need a separate nan check
if(value >= 1.0)
{
Expand All @@ -805,10 +806,10 @@ bool Interpreter::PopulatePerformanceConstraintsFromParams(std::vector<Evaluable
}
//populate maxOpcodeExecutionDepth
perf_constraints.maxOpcodeExecutionDepth = 0;
size_t stack_depth_allowed_offset = perf_constraint_param_offset + 2;
if(params.size() > stack_depth_allowed_offset)
size_t max_opcode_execution_depth_offset = perf_constraint_param_offset + 2;
if(params.size() > max_opcode_execution_depth_offset)
{
double value = InterpretNodeIntoNumberValue(params[stack_depth_allowed_offset]);
double value = InterpretNodeIntoNumberValue(params[max_opcode_execution_depth_offset]);
//nan will fail, so don't need a separate nan check
if(value >= 1.0)
{
Expand All @@ -817,10 +818,61 @@ bool Interpreter::PopulatePerformanceConstraintsFromParams(std::vector<Evaluable
}
}

perf_constraints.entityToConstrainFrom = nullptr;
perf_constraints.constrainMaxContainedEntities = false;
perf_constraints.maxContainedEntities = 0;
perf_constraints.constrainMaxContainedEntityDepth = false;
perf_constraints.maxContainedEntityDepth = 0;
perf_constraints.maxEntityIdLength = 0;

if(include_entity_constraints)
{
//populate maxContainedEntities
size_t max_contained_entities_offset = perf_constraint_param_offset + 3;
if(params.size() > max_contained_entities_offset)
{
double value = InterpretNodeIntoNumberValue(params[max_contained_entities_offset]);
//nan will fail, so don't need a separate nan check
if(value >= 0.0)
{
perf_constraints.constrainMaxContainedEntities = true;
perf_constraints.maxContainedEntities = static_cast<ExecutionCycleCount>(value);
any_constraints = true;
}
}

//populate maxContainedEntityDepth
size_t max_contained_entity_depth_offset = perf_constraint_param_offset + 4;
if(params.size() > max_contained_entity_depth_offset)
{
double value = InterpretNodeIntoNumberValue(params[max_contained_entity_depth_offset]);
//nan will fail, so don't need a separate nan check
if(value >= 0.0)
{
perf_constraints.constrainMaxContainedEntityDepth = true;
perf_constraints.maxContainedEntityDepth = static_cast<ExecutionCycleCount>(value);
any_constraints = true;
}
}

//populate maxEntityIdLength
size_t max_entity_id_length_offset = perf_constraint_param_offset + 5;
if(params.size() > max_entity_id_length_offset)
{
double value = InterpretNodeIntoNumberValue(params[max_entity_id_length_offset]);
//nan will fail, so don't need a separate nan check
if(value >= 1.0)
{
perf_constraints.maxEntityIdLength = static_cast<ExecutionCycleCount>(value);
any_constraints = true;
}
}
}

return any_constraints;
}

void Interpreter::PopulatePerformanceCounters(PerformanceConstraints *perf_constraints)
void Interpreter::PopulatePerformanceCounters(PerformanceConstraints *perf_constraints, Entity *entity_to_constrain_from)
{
if(perf_constraints == nullptr)
return;
Expand Down Expand Up @@ -892,6 +944,67 @@ void Interpreter::PopulatePerformanceCounters(PerformanceConstraints *perf_const
perf_constraints->maxOpcodeExecutionDepth = 1;
}
}

if(entity_to_constrain_from == nullptr)
return;

perf_constraints->entityToConstrainFrom = entity_to_constrain_from;

if(performanceConstraints != nullptr && performanceConstraints->constrainMaxContainedEntities
&& performanceConstraints->entityToConstrainFrom != nullptr)
{
perf_constraints->constrainMaxContainedEntities = true;

//if calling a contained entity, figure out how many this one can create
size_t max_entities = performanceConstraints->maxContainedEntities;
if(performanceConstraints->entityToConstrainFrom->DoesDeepContainEntity(perf_constraints->entityToConstrainFrom))
{
auto erbr = performanceConstraints->entityToConstrainFrom->GetAllDeeplyContainedEntityReferencesGroupedByDepth<EntityReadReference>();
size_t container_total_entities = erbr->size();
erbr.Clear();
erbr = perf_constraints->entityToConstrainFrom->GetAllDeeplyContainedEntityReferencesGroupedByDepth<EntityReadReference>();
size_t contained_total_entities = erbr->size();
erbr.Clear();

if(container_total_entities >= performanceConstraints->maxContainedEntities)
max_entities = 0;
else
max_entities = performanceConstraints->maxContainedEntities - (container_total_entities - contained_total_entities);
}

perf_constraints->maxContainedEntities = std::min(perf_constraints->maxContainedEntities, max_entities);
}

if(performanceConstraints != nullptr && performanceConstraints->constrainMaxContainedEntityDepth
&& performanceConstraints->entityToConstrainFrom != nullptr)
{
perf_constraints->constrainMaxContainedEntityDepth = true;

size_t max_depth = performanceConstraints->maxContainedEntityDepth;
size_t cur_depth = 0;
if(performanceConstraints->entityToConstrainFrom->DoesDeepContainEntity(perf_constraints->entityToConstrainFrom))
{
for(Entity *cur_entity = perf_constraints->entityToConstrainFrom;
cur_entity != performanceConstraints->entityToConstrainFrom;
cur_entity = cur_entity->GetContainer())
cur_depth++;
}

if(cur_depth >= max_depth)
perf_constraints->maxContainedEntityDepth = 0;
else
perf_constraints->maxContainedEntityDepth = std::min(perf_constraints->maxContainedEntityDepth,
max_depth - cur_depth);
}

if(performanceConstraints != nullptr && performanceConstraints->maxEntityIdLength > 0)
{
if(perf_constraints->maxEntityIdLength > 0)
perf_constraints->maxEntityIdLength = std::min(perf_constraints->maxEntityIdLength,
performanceConstraints->maxEntityIdLength);
else
perf_constraints->maxNumAllocatedNodes = performanceConstraints->maxEntityIdLength;
}
}


Expand Down
Loading

0 comments on commit ebf48b2

Please sign in to comment.