Skip to content

Commit

Permalink
Diagnostic new feature - corrupted cache (#1762)
Browse files Browse the repository at this point in the history
This is new diagnostic feature which could help developers to analyze inconsistent query results. Inconsistent means, that there is mix of managed and detached entities in the query result by logical error in their code. It should happens if JPA L2 caching is enabled.
E.g. let's have following code:

em.remove(branchBDiagnostic);
commitTransaction(em);
//branchBDiagnostic is in Detached state
...
em.getTransaction().begin();
branchADiagnostic.getBranchBs().add(branchBDiagnostic);
branchBDiagnostic.setBranchA(branchADiagnostic);
//Detached entity (branchBDiagnostic) is not persisted again - logical error
commitTransaction(em);

//This em.find() will resolve objects from cache
BranchADiagnostic branchADiagnosticFindResult = em.find(BranchADiagnostic.class, BRANCHA_ID);
...

if cache validation is enabled by
persistence unit property
<property name="eclipselink.query-results-cache.validation" value="true"/>
or by query hint
query.setHint("eclipselink.query-results-cache.validation", true);
EclipseLink will print into log output messages like

[EL Warning]: cache: 2022-12-07 14:26:50.86--UnitOfWork(1211586911)--stack of visited objects that refer to the corrupt object: [BranchADiagnostic{id=1}]
[EL Warning]: cache: 2022-12-07 14:26:50.86--UnitOfWork(1211586911)--corrupt object referenced through mapping: org.eclipse.persistence.mappings.OneToManyMapping[branchBs]
[EL Warning]: cache: 2022-12-07 14:26:50.86--UnitOfWork(1211586911)--corrupt object: BranchBDiagnostic{id=11}

Note: <Entity>.toString() method is used.

Signed-off-by: Radek Felcman <radek.felcman@oracle.com>
  • Loading branch information
rfelcman committed Dec 12, 2022
1 parent 6d10143 commit 2abc9fd
Show file tree
Hide file tree
Showing 16 changed files with 700 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -4146,6 +4146,21 @@ public class PersistenceUnitProperties {
*/
public static final String CONCURRENCY_SEMAPHORE_LOG_TIMEOUT = "eclipselink.concurrency.semaphore.log.timeout";

/**
* <p>
* This property control (enable/disable) query result cache validation in {@link org.eclipse.persistence.internal.sessions.UnitOfWorkImpl#internalExecuteQuery}
* </p>
* This can be used to help debugging an object identity problem. An object identity problem is when an managed/active entity in the cache references an entity not in managed state.
* This method will validate that objects in query results (object tree) are in a correct state. As a result there are new log messages in the log.
* It's related with "read" queries like <code>em.find(...);</code> or JPQL queries like <code>SELECT e FROM Entity e</code>.
* It should be controlled at query level too by query hint {@link org.eclipse.persistence.config.QueryHints#QUERY_RESULTS_CACHE_VALIDATION}
* <ul>
* <li>"<code>true</code>" - validate query result object tree and if content is not valid print diagnostic messages. In this case there should be negative impact to the performance.
* <li>"<code>false</code>" (DEFAULT) - don't validate and print any diagnostic messages
* </ul>
*/
public static final String QUERY_RESULTS_CACHE_VALIDATION = "eclipselink.query-results-cache.validation";

/**
* INTERNAL: The following properties will not be displayed through logging
* but instead have an alternate value shown in the log.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,22 @@ public class QueryHints {
*/
public static final String QUERY_RESULTS_CACHE_EXPIRY_TIME_OF_DAY = "eclipselink.query-results-cache.expiry-time-of-day";

/**
* <p>
* This property control (enable/disable) query result cache validation in {@link org.eclipse.persistence.internal.sessions.UnitOfWorkImpl#internalExecuteQuery}
* </p>
* This can be used to help debugging an object identity problem. An object identity problem is when an managed/active entity in the cache references an entity not in managed state.
* This method will validate that objects in query results are in a correct state. As a result there are new log messages in the log.
* It's related with "read" queries like <code>em.find(...);</code> or JPQL queries like <code>SELECT e FROM Entity e</code>.
* It should be controlled at persistence unit level too by persistence unit property {@link org.eclipse.persistence.config.PersistenceUnitProperties#QUERY_RESULTS_CACHE_VALIDATION}
* <ul>
* <li>"<code>true</code>" - validate query result object tree and if content is not valid print diagnostic messages. In this case there should be negative impact to the performance.
* <li>"<code>false</code>" (DEFAULT) - don't validate and print any diagnostic messages
* </ul>
*/
public static final String QUERY_RESULTS_CACHE_VALIDATION = "eclipselink.query-results-cache.validation";


/**
* "eclipselink.query.redirector"
* <p>Used to provide a QueryRedirector to the executing query.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,11 @@ public class LoggingLocalizationResource extends ListResourceBundle {
{ "dbws_xml_schema_read_error", "The [{0}] XML schema could not be read."},
{ "dbws_orm_metadata_read_error", "The [{0}] ORM metadata could not be read."},
{ "dbws_oxm_metadata_read_error", "The [{0}] OXM metadata could not be read."},
{ "dbws_no_wsdl_inline_schema", "The [{0}] WSDL inline schema could not be read."}
{ "dbws_no_wsdl_inline_schema", "The [{0}] WSDL inline schema could not be read."},
{ "validate_object_space", "validate object space." },
{ "stack_of_visited_objects_that_refer_to_the_corrupt_object", "stack of visited objects that refer to the corrupt object: {0}" },
{ "corrupt_object_referenced_through_mapping", "corrupt object referenced through mapping: {0}" },
{ "corrupt_object", "corrupt object: {0}" }
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3007,9 +3007,29 @@ public Object internalExecuteQuery(DatabaseQuery query, AbstractRecord databaseR
}
Object result = query.executeInUnitOfWork(this, databaseRow);
executeDeferredEvents();
if (query instanceof ReadQuery && (project.isAllowQueryResultsCacheValidation() || ((ReadQuery)query).shouldAllowQueryResultsCacheValidation())) {
validateObjectTree(result);
}
return result;
}

private void validateObjectTree(Object startNode) {
log(SessionLog.INFO, SessionLog.TRANSACTION, "validate_object_space");
// This defines an inner class for process the iteration operation, don't be scared, it's just an inner class.
DescriptorIterator iterator = new DescriptorIterator() {
@Override
public void iterate(Object object) {
if (object != null && !isObjectRegistered(object) && getVisitedStack() != null && getVisitedStack().size() > 0) {
log(SessionLog.WARNING, SessionLog.CACHE, "stack_of_visited_objects_that_refer_to_the_corrupt_object", getVisitedStack());
log(SessionLog.WARNING, SessionLog.CACHE, "corrupt_object_referenced_through_mapping", getCurrentMapping());
log(SessionLog.WARNING, SessionLog.CACHE, "corrupt_object", object);
}
}
};
iterator.setSession(this);
iterator.startIterationOn(startNode);
}

/**
* INTERNAL:
* Register the object with the unit of work.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public abstract class ReadQuery extends DatabaseQuery {
/** Stores the JPA maxResult settings for a NamedQuery */
protected int maxResults = -1;

/** Flag that allows query result cache validation or not.*/
protected boolean allowQueryResultsCacheValidation = false;

/**
* PUBLIC:
* Initialize the state of the query
Expand Down Expand Up @@ -480,4 +483,12 @@ public boolean shouldCacheQueryResults() {
public void setTemporaryCachedQueryResults(Object queryResults){
temporaryCachedQueryResults = queryResults;
}

public boolean shouldAllowQueryResultsCacheValidation() {
return allowQueryResultsCacheValidation;
}

public void setAllowQueryResultsCacheValidation(boolean allowQueryResultsCacheValidation) {
this.allowQueryResultsCacheValidation = allowQueryResultsCacheValidation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ public class Project extends CoreProject<ClassDescriptor, Login, DatabaseSession
/** Flag that allows add to extended thread logging output thread stack trace or not.*/
protected boolean allowExtendedThreadLoggingThreadDump = false;

/** Flag that allows query result cache validation or not.*/
protected boolean allowQueryResultsCacheValidation = false;

/**
* Mapped Superclasses (JPA 2) collection of parent non-relational descriptors keyed on MetadataClass
* without creating a compile time dependency on JPA.
Expand Down Expand Up @@ -1356,6 +1359,14 @@ public boolean allowExtendedThreadLoggingThreadDump() {
return this.allowExtendedThreadLoggingThreadDump;
}

/**
* INTERNAL:
* Flag that allows query result cache validation or not. If true result is presented via log messages.
*/
public boolean isAllowQueryResultsCacheValidation() {
return allowQueryResultsCacheValidation;
}

/**
* PUBLIC:
* Return the descriptor for the alias
Expand Down Expand Up @@ -1435,6 +1446,14 @@ public void setAllowExtendedThreadLoggingThreadDump(boolean allowExtendedThreadL
this.allowExtendedThreadLoggingThreadDump = allowExtendedThreadLoggingThreadDump;
}

/**
* INTERNAL:
* Set to true to enable query result cache validation or not. Result is presented via log messages.
*/
public void setAllowQueryResultsCacheValidation(boolean allowQueryResultsCacheValidation) {
this.allowQueryResultsCacheValidation = allowQueryResultsCacheValidation;
}

/**
* INTERNAL:
* Indicates whether there is at least one descriptor that has at least on mapping that
Expand Down
75 changes: 75 additions & 0 deletions jpa/eclipselink.jpa.testapps/jpa.test.diagnostic/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved.
This program and the accompanying materials are made available under the
terms of the Eclipse Public License v. 2.0 which is available at
http://www.eclipse.org/legal/epl-2.0,
or the Eclipse Distribution License v. 1.0 which is available at
http://www.eclipse.org/org/documents/edl-v10.php.
SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
-->

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>org.eclipse.persistence.jpa.testapps</artifactId>
<groupId>org.eclipse.persistence</groupId>
<version>4.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>org.eclipse.persistence.jpa.testapps.diagnostic</artifactId>

<name>Test - diagnostic</name>

<properties>
<argLine/>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<!--Resolve dependencies into Maven properties like ${org.eclipse.persistence:org.eclipse.persistence.jpa:jar} for JPA module-->
<execution>
<id>get-test-classpath-to-properties</id>
<phase>process-test-classes</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.carlspring.maven</groupId>
<artifactId>derby-maven-plugin</artifactId>
<executions>
<execution>
<id>start-derby</id>
<phase>process-test-classes</phase>
</execution>
<execution>
<id>stop-derby</id>
<phase>prepare-package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>default-test</id>
<configuration>
<argLine>-javaagent:${org.eclipse.persistence:org.eclipse.persistence.jpa:jar} @{argLine}</argLine>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/

// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.models.jpa.diagnostic;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;

import java.util.ArrayList;
import java.util.List;

@NamedQueries({
@NamedQuery(name = "findBranchADiagnosticById", query = "SELECT bd FROM BranchADiagnostic bd " + "WHERE bd.id = :id")}
)
@Entity
@Table(name = "BRANCHA_DIAGNOSTIC")
public class BranchADiagnostic {
protected int id;

protected List<BranchBDiagnostic> branchBs;

public BranchADiagnostic() {
this.branchBs = new ArrayList<>();
}
/**
* @return the id
*/
@Id
public int getId() {
return id;
}

/**
* @param id
* the id to set
*/
public void setId(int id) {
this.id = id;
}

/**
* @return the branchBs
*/
@OneToMany(mappedBy = "branchA")
public List<BranchBDiagnostic> getBranchBs() {
return branchBs;
}

/**
* @param branchBs
* the branchBs to set
*/
public void setBranchBs(List<BranchBDiagnostic> branchBs) {
this.branchBs = branchBs;
}

@Override
public String toString() {
return "BranchADiagnostic{" +
"id=" + id + '}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Copyright (c) 2022 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/

// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.testing.models.jpa.diagnostic;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;

/**
* Entity implementation class for Entity: BranchA
*
*/
@Entity
@Table(name = "BRANCHB_DIAGNOSTIC")
public class BranchBDiagnostic {

private static final long serialVersionUID = 1L;

protected int id;
protected BranchADiagnostic branchA;

/**
* @return the id
*/
@Id
public int getId() {
return id;
}

/**
* @param id
* the id to set
*/
public void setId(int id) {
this.id = id;
}

/**
* @return the branchA
*/
@ManyToOne
@JoinColumn(name = "BRANCHA_FK", nullable = true)
public BranchADiagnostic getBranchA() {
return branchA;
}

/**
* @param branchA
* the branchA to set
*/
public void setBranchA(BranchADiagnostic branchA) {
this.branchA = branchA;
}

@Override
public String toString() {
return "BranchBDiagnostic{" +
"id=" + id + '}';
}
}

0 comments on commit 2abc9fd

Please sign in to comment.