Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.time.Duration;
import java.time.Instant;
import java.time.format.DateTimeParseException;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -37,12 +38,12 @@ public class InitialLimitedDurationErrorInjectionPolicy
LoggerFactory.getLogger(InitialLimitedDurationErrorInjectionPolicy.class);
private static final long serialVersionUID = 1L;

private Instant startTime;
private static Instant startTime;
Comment thread
aasthabharill marked this conversation as resolved.
Outdated
private final Duration injectionDuration;
private final String effectiveDurationParameter;
private String errorCodeToBeInjected;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The errorCodeToBeInjected field is not initialized with a default value. When the errorCode parameter is blank or missing in the input JSON, the constructor logs that it is using the default DEADLINE_EXCEEDED, but it does not actually assign a value to errorCodeToBeInjected, leaving it as null. Initializing it to Code.DEADLINE_EXCEEDED.name() by default ensures that the policy behaves as documented and avoids potential NullPointerExceptions in the caller.

Suggested change
private String errorCodeToBeInjected;
private String errorCodeToBeInjected = Code.DEADLINE_EXCEEDED.name();

private Clock clock;
private long callCount;
private static final AtomicLong callCount = new AtomicLong(0);

private static final String DEFAULT_DURATION = "PT10M";
private static final String DURATION_FIELD_IN_OBJECT = "duration";
Expand Down Expand Up @@ -124,22 +125,20 @@ public InitialLimitedDurationErrorInjectionPolicy(JsonNode inputParameter, Clock
*/
@Override
public boolean shouldInjectionError() {
if (this.startTime == null) {
if (startTime == null) {
synchronized (this) {
if (this.startTime == null) {
this.startTime = Instant.now(clock);
if (startTime == null) {
startTime = Instant.now(clock);
Comment on lines +129 to +131

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are synchronising on the class now instead of the object of the class? Do I understand that right? What was the issue happening earlier?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was happening earlier:
The original issue was that Beam actually deserializes multiple instances of this policy per worker (usually one per thread or bundle). When the variables weren't static, each instance got its own isolated timer. This meant the duration kept resetting per-thread instead of applying globally - so i kept getting the same fake exception for each retry ultimately failing the job.

I fixed that by making the state static so it's shared across the worker. But once the state is static, locking on this (the instance) is unsafe because threads using different instances would acquire different locks, leading to race conditions. Synchronizing on the Class object ensures all instances share the exact same lock to initialize the global timer safely.

LOG.info(
"First call detected. Errors will be injected for {} starting from {}.",
this.injectionDuration,
this.startTime);
startTime);
}
}
}
Comment thread
aasthabharill marked this conversation as resolved.
synchronized (this) {
++callCount;
}
long currentCallCount = callCount.incrementAndGet();

if (callCount < INITIAL_ALLOWED_CALLS_COUNT) {
if (currentCallCount < INITIAL_ALLOWED_CALLS_COUNT) {
return false;
}

Expand Down
181 changes: 108 additions & 73 deletions v2/gcs-spanner-dv/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,82 +16,117 @@
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~-->

<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.google.cloud.teleport.v2</groupId>
<artifactId>dynamic-templates</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<parent>
<groupId>com.google.cloud.teleport.v2</groupId>
<artifactId>dynamic-templates</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<artifactId>gcs-spanner-dv</artifactId>
<artifactId>gcs-spanner-dv</artifactId>

<dependencies>
<dependency>
<groupId>com.google.cloud.teleport.v2</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-core</artifactId>
</dependency>
<dependencies>
<dependency>
<groupId>com.google.cloud.teleport.v2</groupId>
<artifactId>common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.google.cloud</groupId>
<artifactId>google-cloud-core</artifactId>
</dependency>

<!-- Test Dependencies -->
<dependency>
<groupId>com.google.cloud.teleport</groupId>
<artifactId>it-google-cloud-platform</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.beam</groupId>
<artifactId>beam-it-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.cloud.teleport.v2</groupId>
<artifactId>spanner-common</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>LATEST</version> <!-- Use the latest version available -->
<scope>test</scope>
</dependency>
</dependencies>
<!-- Test Dependencies -->
<dependency>
<groupId>com.google.cloud.teleport</groupId>
<artifactId>it-google-cloud-platform</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.beam</groupId>
<artifactId>beam-it-jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.cloud.teleport.v2</groupId>
<artifactId>spanner-common</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>LATEST</version> <!-- Use the latest version available -->
<scope>test</scope>
</dependency>
</dependencies>

<!-- Jacoco configuration -->
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<configuration>
<excludes combine.children="append">
<!-- combine.children appends to the existing exclusions in the
parent POM(s). -->
<exclude>com/google/cloud/teleport/v2/dto/**</exclude>
<exclude>com/google/cloud/teleport/v2/constants/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<!-- Jacoco configuration -->
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco.version}</version>
<configuration>
<excludes combine.children="append">
<!-- combine.children appends to the existing exclusions in the
parent POM(s). -->
<exclude>com/google/cloud/teleport/v2/dto/**</exclude>
<exclude>com/google/cloud/teleport/v2/constants/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>

<profiles>
<profile>
<id>useRealSpanner</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>!activateFailureInjection</name>
</property>
</activation>
<dependencies>
<dependency>
<groupId>com.google.cloud.teleport.v2</groupId>
<artifactId>real-spanner-service</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>failureInjectionTest</id>
<activation>
<property>
<name>activateFailureInjection</name>
<value>true</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>com.google.cloud.teleport.v2</groupId>
<artifactId>failure-injected-spanner-service</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import org.apache.beam.sdk.Pipeline;
import org.apache.beam.sdk.PipelineResult;
import org.apache.beam.sdk.io.gcp.spanner.SpannerConfig;
import org.apache.beam.sdk.io.gcp.spanner.SpannerServiceFactoryImpl;
import org.apache.beam.sdk.options.Default;
import org.apache.beam.sdk.options.PipelineOptions;
import org.apache.beam.sdk.options.PipelineOptionsFactory;
Expand Down Expand Up @@ -254,6 +255,16 @@ public interface Options extends PipelineOptions {
String getTransformationCustomParameters();

void setTransformationCustomParameters(String value);

@TemplateParameter.Text(
order = 16,
optional = true,
description = "Failure injection parameter",
helpText = "Failure injection parameter. Only used for testing.")
@Default.String("")
String getFailureInjectionParameter();

void setFailureInjectionParameter(String value);
}

public static void main(String[] args) {
Expand Down Expand Up @@ -327,11 +338,20 @@ public static PipelineResult run(Options options) {

@VisibleForTesting
static SpannerConfig createSpannerConfig(Options options) {
return SpannerConfig.create()
.withProjectId(ValueProvider.StaticValueProvider.of(options.getProjectId()))
.withHost(ValueProvider.StaticValueProvider.of(options.getSpannerHost()))
.withInstanceId(ValueProvider.StaticValueProvider.of(options.getInstanceId()))
.withDatabaseId(ValueProvider.StaticValueProvider.of(options.getDatabaseId()))
.withRpcPriority(ValueProvider.StaticValueProvider.of(options.getSpannerPriority()));
SpannerConfig config =
SpannerConfig.create()
.withProjectId(ValueProvider.StaticValueProvider.of(options.getProjectId()))
.withHost(ValueProvider.StaticValueProvider.of(options.getSpannerHost()))
.withInstanceId(ValueProvider.StaticValueProvider.of(options.getInstanceId()))
.withDatabaseId(ValueProvider.StaticValueProvider.of(options.getDatabaseId()))
.withRpcPriority(ValueProvider.StaticValueProvider.of(options.getSpannerPriority()));

if (options.getFailureInjectionParameter() != null
&& !options.getFailureInjectionParameter().isEmpty()) {
config =
SpannerServiceFactoryImpl.createSpannerService(
config, options.getFailureInjectionParameter());
}
return config;
}
}
Loading
Loading