Skip to content

Commit

Permalink
AVRO-3713: [Java] Fix Map synchronization regression
Browse files Browse the repository at this point in the history
Signed-off-by: Niels Basjes <[email protected]>
  • Loading branch information
nielsbasjes committed Feb 17, 2023
1 parent 72e38dc commit ad5cf68
Show file tree
Hide file tree
Showing 8 changed files with 2,840 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,14 @@
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentMap;

import org.apache.avro.AvroMissingFieldException;
import org.apache.avro.AvroRuntimeException;
Expand All @@ -59,6 +58,9 @@
import org.apache.avro.util.internal.Accessor;

import com.fasterxml.jackson.databind.JsonNode;
import org.apache.avro.util.springframework.ConcurrentReferenceHashMap;

import static org.apache.avro.util.springframework.ConcurrentReferenceHashMap.ReferenceType.WEAK;

/**
* Utilities for generic Java data. See {@link GenericRecordBuilder} for a
Expand Down Expand Up @@ -1213,7 +1215,7 @@ protected int compare(Object o1, Object o2, Schema s, boolean equals) {
}
}

private final Map<Field, Object> defaultValueCache = Collections.synchronizedMap(new WeakHashMap<>());
private final ConcurrentMap<Field, Object> defaultValueCache = new ConcurrentReferenceHashMap<>(128, WEAK);

/**
* Gets the default value of the given field, if any.
Expand All @@ -1233,28 +1235,20 @@ public Object getDefaultValue(Field field) {
}

// Check the cache
Object defaultValue = defaultValueCache.get(field);

// If not cached, get the default Java value by encoding the default JSON
// value and then decoding it:
if (defaultValue == null)
return defaultValueCache.computeIfAbsent(field, fieldToGetValueFor -> {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BinaryEncoder encoder = EncoderFactory.get().binaryEncoder(baos, null);
Accessor.encode(encoder, field.schema(), json);
Accessor.encode(encoder, fieldToGetValueFor.schema(), json);
encoder.flush();
BinaryDecoder decoder = DecoderFactory.get().binaryDecoder(baos.toByteArray(), null);
defaultValue = createDatumReader(field.schema()).read(null, decoder);

// this MAY result in two threads creating the same defaultValue
// and calling put. The last thread will win. However,
// that's not an issue.
defaultValueCache.put(field, defaultValue);
return createDatumReader(fieldToGetValueFor.schema()).read(null, decoder);
} catch (IOException e) {
throw new AvroRuntimeException(e);
}

return defaultValue;
});
}

private static final Schema STRINGS = Schema.create(Type.STRING);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed 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
*
* https://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.avro.util.springframework;

import org.apache.avro.reflect.Nullable;

/**
* Assertion utility class that assists in validating arguments.
*
* <p>
* Useful for identifying programmer errors early and clearly at runtime.
*
* <p>
* For example, if the contract of a public method states it does not allow
* {@code null} arguments, {@code Assert} can be used to validate that contract.
* Doing this clearly indicates a contract violation when it occurs and protects
* the class's invariants.
*
* <p>
* Typically used to validate method arguments rather than configuration
* properties, to check for cases that are usually programmer errors rather than
* configuration errors. In contrast to configuration initialization code, there
* is usually no point in falling back to defaults in such methods.
*
* <p>
* This class is similar to JUnit's assertion library. If an argument value is
* deemed invalid, an {@link IllegalArgumentException} is thrown (typically).
* For example:
*
* <pre class="code">
* Assert.notNull(clazz, "The class must not be null");
* Assert.isTrue(i &gt; 0, "The value must be greater than zero");
* </pre>
*
* <p>
* Mainly for internal use within the framework; for a more comprehensive suite
* of assertion utilities consider {@code org.apache.commons.lang3.Validate}
* from <a href="https://commons.apache.org/proper/commons-lang/">Apache Commons
* Lang</a>, Google Guava's <a href=
* "https://github.com/google/guava/wiki/PreconditionsExplained">Preconditions</a>,
* or similar third-party libraries.
*
* @author Keith Donald
* @author Juergen Hoeller
* @author Sam Brannen
* @author Colin Sampaleanu
* @author Rob Harrop
* @since 1.1.2
*/
class Assert {
private Assert() {
}

/**
* Assert a boolean expression, throwing an {@code IllegalStateException} if the
* expression evaluates to {@code false}.
*
* <pre class="code">
* Assert.state(id == null, "The id property must not already be initialized");
* </pre>
*
* @param expression a boolean expression
* @param message the exception message to use if the assertion fails
* @throws IllegalStateException if {@code expression} is {@code false}
*/
public static void state(boolean expression, String message) {
if (!expression) {
throw new IllegalStateException(message);
}
}

/**
* Assert a boolean expression, throwing an {@code IllegalArgumentException} if
* the expression evaluates to {@code false}.
*
* <pre class="code">
* Assert.isTrue(i &gt; 0, "The value must be greater than zero");
* </pre>
*
* @param expression a boolean expression
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if {@code expression} is {@code false}
*/
public static void isTrue(boolean expression, String message) {
if (!expression) {
throw new IllegalArgumentException(message);
}
}

/**
* Assert that an object is not {@code null}.
*
* <pre class="code">
* Assert.notNull(clazz, "The class must not be null");
* </pre>
*
* @param object the object to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the object is {@code null}
*/
public static void notNull(@Nullable Object object, String message) {
if (object == null) {
throw new IllegalArgumentException(message);
}
}

}
Loading

0 comments on commit ad5cf68

Please sign in to comment.