All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.lra.coordinator.LraImpl Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2021, 2024 Oracle and/or its affiliates.
 *
 * 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
 *
 *     http://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 io.helidon.lra.coordinator;

import java.lang.System.Logger.Level;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import io.helidon.common.LazyValue;
import io.helidon.config.Config;
import io.helidon.http.ClientRequestHeaders;
import io.helidon.metrics.api.Counter;
import io.helidon.metrics.api.MeterRegistry;
import io.helidon.metrics.api.Metrics;
import io.helidon.metrics.api.Timer;

import org.eclipse.microprofile.lra.annotation.LRAStatus;

class LraImpl implements Lra {

    private static final System.Logger LOGGER = System.getLogger(LraImpl.class.getName());

    private final LazyValue coordinatorURL;
    private final Set compensatorLinks = Collections.synchronizedSet(new HashSet<>());
    private final String lraId;
    private final Config config;
    private final List children = Collections.synchronizedList(new ArrayList<>());
    private final List participants = new CopyOnWriteArrayList<>();
    private final AtomicReference status = new AtomicReference<>(LRAStatus.Active);
    private final Lock lock = new ReentrantLock();
    private final MeterRegistry registry = Metrics.globalRegistry();
    private final Counter lraCtr = registry.getOrCreate(Counter.builder("lractr"));
    private final Timer lrfLifeSpanTmr = registry.getOrCreate(Timer.builder("lralifespantmr"));
    private final Timer.Sample lraLifeSpanTmrSample = Timer.start(registry);
    private long timeout;
    private URI parentId;
    private boolean isChild;
    private long whenReadyToDelete = 0;

    LraImpl(CoordinatorService coordinatorService, String lraUUID, Config config) {
        lraId = lraUUID;
        this.config = config;
        lraCtr.increment();
        coordinatorURL = LazyValue.create(coordinatorService.coordinatorURL());
    }

    LraImpl(CoordinatorService coordinatorService, String lraUUID, URI parentId, Config config) {
        lraId = lraUUID;
        this.parentId = parentId;
        this.config = config;
        lraCtr.increment();
        coordinatorURL = LazyValue.create(coordinatorService.coordinatorURL());
    }

    @Override
    public String lraId() {
        return lraId;
    }

    @Override
    public String parentId() {
        return Optional.ofNullable(parentId).map(URI::toASCIIString).orElse(null);
    }

    @Override
    public boolean isChild() {
        return isChild;
    }

    void setChild(boolean child) {
        isChild = child;
    }

    @Override
    public long timeout() {
        return timeout;
    }

    void setTimeout(long timeout) {
        this.timeout = timeout;
    }

    @Override
    public List participants() {
        return this.participants.stream().map(Participant.class::cast).toList();
    }

    @Override
    public LRAStatus status() {
        return lraStatus().get();
    }

    void setStatus(LRAStatus status) {
        this.status.set(status);
    }

    long getWhenReadyToDelete() {
        return this.whenReadyToDelete;
    }

    void setWhenReadyToDelete(long whenReadyToDelete) {
        this.whenReadyToDelete = whenReadyToDelete;
    }

    void addParticipant(ParticipantImpl participant) {
        this.participants.add(participant);
    }

    void setupTimeout(long timeLimit) {
        if (timeLimit != 0) {
            this.timeout = System.currentTimeMillis() + timeLimit;
        } else {
            this.timeout = 0;
        }
    }

    boolean checkTimeout() {
        return timeout > 0 && timeout < System.currentTimeMillis();
    }

    void addParticipant(String compensatorLink) {
        if (compensatorLinks.add(compensatorLink)) {
            ParticipantImpl participant = new ParticipantImpl(config);
            participant.parseCompensatorLinks(compensatorLink);
            participants.add(participant);
        }
    }

    void removeParticipant(String compensatorUrl) {
        Set forRemove = participants.stream()
                .filter(p -> p.equalCompensatorUris(compensatorUrl))
                .collect(Collectors.toSet());
        forRemove.forEach(participants::remove);
    }

    void addChild(LraImpl lra) {
        children.add(lra);
        lra.isChild = true;
    }

    Consumer headers() {
        return headers -> {
            headers.add(LRA_HTTP_CONTEXT_HEADER_NAME, lraContextId());
            headers.add(LRA_HTTP_ENDED_CONTEXT_HEADER_NAME, lraContextId());
            Optional.ofNullable(parentId)
                    .map(URI::toASCIIString)
                    .ifPresent(s -> headers.add(LRA_HTTP_PARENT_CONTEXT_HEADER_NAME, s));
            headers.add(LRA_HTTP_RECOVERY_HEADER_NAME, lraContextId() + "/recovery");
        };
    }

    void close() {
        Set allowedStatuses = Set.of(LRAStatus.Active, LRAStatus.Closing);
        if (LRAStatus.Closing != status.updateAndGet(old -> allowedStatuses.contains(old) ? LRAStatus.Closing : old)) {
            LOGGER.log(Level.WARNING, "Can't close LRA, it's already " + status.get().name() + " " + this.lraId);
            return;
        }
        lraLifeSpanTmrSample.stop(lrfLifeSpanTmr);
        if (lock.tryLock()) {
            try {
                sendComplete();
                // needs to go before nested close, so we know if nested was already closed
                // or not(non closed nested can't get @Forget call)
                forgetNested();
                for (LraImpl nestedLra : children) {
                    nestedLra.close();
                }
                trySendAfterLRA();
                markForDeletion();
            } finally {
                lock.unlock();
            }
        }
    }

    void cancel() {
        Set allowedStatuses = Set.of(LRAStatus.Active, LRAStatus.Cancelling);
        if (LRAStatus.Cancelling != status.updateAndGet(old -> allowedStatuses.contains(old) ? LRAStatus.Cancelling : old)
                && !isChild) { // nested can be compensated even if closed
            LOGGER.log(Level.WARNING, "Can't cancel LRA, it's already " + status.get().name() + " " + this.lraId);
            return;
        }
        lraLifeSpanTmrSample.stop(lrfLifeSpanTmr);
        for (LraImpl nestedLra : children) {
            nestedLra.cancel();
        }
        if (lock.tryLock()) {
            try {
                sendCancel();
                trySendAfterLRA();
                trySendForgetLRA();
                markForDeletion();
            } finally {
                lock.unlock();
            }
        }
    }

    void triggerTimeout() {
        for (LraImpl nestedLra : children) {
            if (nestedLra.participants.stream().anyMatch(p -> p.state().isFinal() || p.isListenerOnly())) {
                nestedLra.triggerTimeout();
            }
        }
        cancel();
        if (lock.tryLock()) {
            try {
                trySendAfterLRA();
            } finally {
                lock.unlock();
            }
        }
    }

    boolean tryAfter() {
        if (lock.tryLock()) {
            try {
                return trySendAfterLRA();
            } finally {
                lock.unlock();
            }
        }
        return false;
    }

    boolean tryForget() {
        if (lock.tryLock()) {
            try {
                return trySendForgetLRA();
            } finally {
                lock.unlock();
            }
        }
        return false;
    }

    AtomicReference lraStatus() {
        return status;
    }

    String lraContextId() {
        return coordinatorURL.get().toASCIIString() + "/" + lraId;
    }

    boolean isReadyToDelete() {
        return whenReadyToDelete != 0 && whenReadyToDelete < System.currentTimeMillis();
    }

    void markForDeletion() {
        // delete after 10 minutes
        whenReadyToDelete = (10 * 60 * 1000) + System.currentTimeMillis();
    }

    private boolean forgetNested() {
        for (LraImpl nestedLra : children) {
            //don't do forget not yet closed nested lra
            if (nestedLra.status.get() != LRAStatus.Closed) {
                continue;
            }
            boolean allDone = true;
            for (ParticipantImpl participant : nestedLra.participants) {
                if (participant.forgetURI().isEmpty() || participant.isForgotten()) {
                    continue;
                }
                allDone = participant.sendForget(nestedLra) && allDone;
            }
            if (!allDone) {
                return false;
            }
        }
        return true;
    }

    private boolean trySendForgetLRA() {
        boolean allFinished = true;
        for (ParticipantImpl participant : participants) {
            if (participant.forgetURI().isEmpty() || participant.isForgotten()) {
                continue;
            }
            if (Set.of(
                    ParticipantImpl.Status.FAILED_TO_COMPLETE,
                    ParticipantImpl.Status.FAILED_TO_COMPENSATE
            ).contains(participant.state())) {
                allFinished = participant.sendForget(this) && allFinished;
            }
        }
        return allFinished;
    }

    private void sendComplete() {
        boolean allClosed = true;
        for (ParticipantImpl participant : participants) {
            if (participant.isInEndStateOrListenerOnly() && !isChild) {
                continue;
            }
            allClosed = participant.sendComplete(this) && allClosed;
        }
        if (allClosed) {
            this.lraStatus().compareAndSet(LRAStatus.Closing, LRAStatus.Closed);
        }
    }

    private void sendCancel() {
        boolean allDone = true;
        for (ParticipantImpl participant : participants) {
            if (participant.isInEndStateOrListenerOnly() && !isChild) {
                continue;
            }
            allDone = participant.sendCancel(this) && allDone;
        }
        if (allDone) {
            this.lraStatus().compareAndSet(LRAStatus.Cancelling, LRAStatus.Cancelled);
        }
    }

    private boolean trySendAfterLRA() {
        boolean allSent = true;
        for (ParticipantImpl participant : participants) {
            allSent = participant.trySendAfterLRA(this) && allSent;
        }
        return allSent;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy