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

com.microsoft.azure.toolkit.lib.common.operation.OperationThreadContext Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License. See License.txt in the project root for license information.
 */

package com.microsoft.azure.toolkit.lib.common.operation;

import com.microsoft.azure.toolkit.lib.common.messager.AzureMessager;
import com.microsoft.azure.toolkit.lib.common.utils.Utils;
import lombok.extern.slf4j.Slf4j;
import lombok.Getter;
import org.apache.commons.lang3.exception.ExceptionUtils;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InterruptedIOException;
import java.util.Objects;
import java.util.Optional;

@Slf4j
public class OperationThreadContext {
    private static final ThreadLocal context = new ThreadLocal<>();

    protected long threadId;
    @Nullable
    protected Operation operation;
    @Getter
    @Nullable
    protected OperationThreadContext parent;

    private OperationThreadContext(@Nullable final OperationThreadContext parent) {
        this.threadId = -1;
        this.setParent(parent);
    }

    private synchronized void setParent(@Nullable OperationThreadContext parent) {
        if (!Objects.equals(this.parent, parent)) {
            this.parent = parent;
            this.operation = Optional.ofNullable(parent).map(p -> p.operation).orElse(null);
        }
    }

    @Nonnull
    public static OperationThreadContext current() {
        OperationThreadContext ctxNode = OperationThreadContext.context.get();
        if (Objects.isNull(ctxNode)) {
            ctxNode = new OperationThreadContext(null);
            OperationThreadContext.context.set(ctxNode);
        }
        return ctxNode;
    }

    @Nullable
    public Operation currentOperation() {
        return this.operation;
    }

    synchronized void pushOperation(final Operation operation) {
        if (Objects.isNull(this.parent) && Objects.isNull(this.operation)) {
            log.debug(String.format("orphan context[%s] is setup", this));
        }
        operation.setParent(this.operation);
        this.operation = operation;
    }

    @Nullable
    synchronized Operation popOperation() {
        final Operation popped = this.operation;
        if (popped == null) { // @wangmi FIXME: just workaround
            log.warn("popped operation is null");
            return null;
        }
        assert popped != null : "popped operation is null";
        this.operation = popped.getParent();
        if (Objects.isNull(this.parent) && Objects.isNull(this.operation)) {
            OperationThreadContext.context.remove();
            log.debug(String.format("orphan context[%s] is disposed", this));
        }
        return popped;
    }

    public void run(final Runnable runnable) {
        try {
            this.setup();
            runnable.run();
        } catch (final Throwable throwable) {
            final Throwable rootCause = ExceptionUtils.getRootCause(throwable);
            if (!(rootCause instanceof InterruptedIOException) && !(rootCause instanceof InterruptedException)) {
                // Swallow interrupted exception caused by unsubscribe
                AzureMessager.getMessager().error(throwable);
            }
        } finally {
            this.dispose();
        }
    }

    @Nonnull
    public OperationThreadContext derive() {
        final long threadId = Thread.currentThread().getId();
        final OperationThreadContext current = OperationThreadContext.current();
        assert this == current : String.format("[threadId:%s] deriving context from context[%s] in context[%s].", threadId, this, current);
        this.threadId = this.threadId > 0 ? this.threadId : threadId;
        return new OperationThreadContext(this);
    }

    private synchronized void setup() {
        final OperationThreadContext current = OperationThreadContext.current();
        final long threadId = Thread.currentThread().getId();
        assert current.threadId == -1 || current.threadId == threadId : String.format("[threadId:%s] illegal thread context[%s]", threadId, current);
        this.threadId = threadId; // we can not decide in which thread this task will run until here.
        if (current.threadId != -1) {
            this.setParent(current);
        }
        OperationThreadContext.context.set(this);
    }

    private synchronized void dispose() {
        final OperationThreadContext current = OperationThreadContext.current();
        final long threadId = Thread.currentThread().getId();
        assert this == current && this.threadId == threadId : String.format("[threadId:%s] disposing context[%s] in context[%s].", threadId, this, current);
        final OperationThreadContext ancestorContext = getAliveAncestor(current);
        if (this.parent == null || this.threadId != this.parent.threadId || Objects.isNull(ancestorContext)) { // this is the root task of current thread.
            OperationThreadContext.context.get().resetThreadId();
            OperationThreadContext.context.remove();
        } else { // this is not the root task of current thread.
            OperationThreadContext.context.get().resetThreadId();
            OperationThreadContext.context.set(ancestorContext);
        }
    }

    private void resetThreadId() {
        this.threadId = -1;
    }

    private OperationThreadContext getAliveAncestor(OperationThreadContext currentContext) {
        OperationThreadContext loopContext = currentContext.parent;
        while (Objects.nonNull(loopContext)) {
            if (loopContext.threadId != -1) {
                return loopContext;
            } else {
                loopContext = loopContext.parent;
            }
        }
        return null;
    }

    public String getId() {
        return Utils.getId(this);
    }

    public String toString() {
        final String id = getId();
        final String prId = Optional.ofNullable(this.parent).map(OperationThreadContext::getId).orElse("/");
        return String.format("{id: %s, threadId:%s, parent:%s}", id, this.threadId, prId);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy