My First Post

Getting Started with Hugo#

Introduction#

This is my first page using AsciiDoc in Hugo! Since you are using the Book theme, this content will appear in the left‑hand navigation menu.

Why AsciiDoc?#

  • It is standard for technical docs.
  • It handles complex tables better than Markdown.
  • It supports “Includes”.

Code Example#

1package main
2
3import "fmt"
4
5func main() {
6    for i := 0; i < 3; i++ {
7        fmt.Println("Value of i:", i)
8    }
9}
package net.lbruun.dbleaderelect.internal.core;

import static net.lbruun.dbleaderelect.LeaderElector.NO_LEADER_LASTSEENTIMESTAMP_MS;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.SQLRecoverableException;
import java.sql.SQLTransientException;
import java.time.Instant;
import javax.sql.DataSource;
import net.lbruun.dbleaderelect.LeaderElectorConfiguration;
import net.lbruun.dbleaderelect.LeaderElectorListener;
import net.lbruun.dbleaderelect.exception.LeaderElectorException;
import net.lbruun.dbleaderelect.exception.LeaderElectorExceptionNonRecoverable;
import net.lbruun.dbleaderelect.exception.LeaderElectorExceptionRecoverable;
import net.lbruun.dbleaderelect.internal.core.RowInLockTable.CurrentLeaderDbStatus;
import net.lbruun.dbleaderelect.internal.events.EventHelpers;
import net.lbruun.dbleaderelect.internal.sql.SQLCmds;
import net.lbruun.dbleaderelect.internal.utils.SQLUtils;

/**
 *
 */
public class SQLLeaderElect {

    private final LeaderElectorConfiguration configuration;
    private final SQLCmds sqlCmds;
    private final String myRoleId;
    private final String myCandidateId;
    private final DataSource dataSource;
    private final String tableNameDisplay;
    private volatile boolean currentlyAmLeader = false;
    private int noOfConsecutiveTransientErrors = 0;
    private volatile boolean hasHadSuccessfulExection = false;
    private boolean hasRelinquishedLeadership = false;

    public SQLLeaderElect(LeaderElectorConfiguration configuration, DataSource dataSource, String tableNameDisplay) {
        this.dataSource = dataSource;
        this.configuration = configuration;
        this.myRoleId = configuration.getRoleId();
        this.myCandidateId = configuration.getCandidateId();
        this.sqlCmds = SQLCmds.getSQL(configuration);
        this.tableNameDisplay = tableNameDisplay;
    }
    
    public boolean isLeader() {
        return currentlyAmLeader;
    }
    
    public void ensureTable() throws SQLException {
        try (Connection connnection = this.dataSource.getConnection()) {
            if (!SQLUtils.tableExists(connnection, configuration.getSchemaName(), configuration.getTableName())) {
                configuration.getLeaderElectorLogger().logInfo(this.getClass(), "Creating table " + tableNameDisplay);
                
                try (PreparedStatement createTableStmt = sqlCmds.getCreateTableStmt(connnection)) {
                    createTableStmt.execute();
                } catch (SQLException ex) {
                    if (!sqlCmds.isTableAlreadyExistException(ex)) {
                        throw ex;
                    }
                }
            }
        }
    }
    
    public void ensureRoleRow() throws SQLException {
        try (Connection connnection = this.dataSource.getConnection()) {
            try (PreparedStatement p = sqlCmds.getInsertRoleStmt(connnection, this.configuration.getRoleId())) {
                p.execute();
            }
        }
    }
    
    private void affirmLeadership(Connection connection, String roleId, String candidateId) 
            throws SQLException, LeaderElectorExceptionNonRecoverable {
        try (PreparedStatement pstmt = sqlCmds.getAffirmLeadershipStmt(connection, roleId, candidateId)) {
            executeUpdate(pstmt);
        }
    }
    
    private void assumeLeadership(Connection connection, String roleId, String candidateId, long newLeaseCounter) 
            throws SQLException, LeaderElectorExceptionNonRecoverable {
        try (PreparedStatement pstmt = sqlCmds.getAssumeLeadershipStmt(connection, roleId, candidateId, newLeaseCounter)) {
            executeUpdate(pstmt);
        }
    }
    
    private void relinquishLeadership(Connection connection, String roleId, String candidateId) 
            throws SQLException, LeaderElectorExceptionNonRecoverable {
        try (PreparedStatement pstmt = sqlCmds.getRelinquishLeadershipStmt(connection, roleId, candidateId)) {
            executeUpdate(pstmt);
        }
    }
    
    private void executeUpdate(PreparedStatement pstmt) 
            throws SQLException, LeaderElectorExceptionNonRecoverable {
        int rowsAffected = pstmt.executeUpdate();
        if (rowsAffected != 1) {
            throw new LeaderElectorExceptionNonRecoverable(rowsAffected + " rows was affected by UPDATE statement. Expected exactly 1 (one) row to be affected.");
        }
    }
    
    private long getNewLeaseCounter(long existingLeaseCounter) {
        return (existingLeaseCounter == Long.MAX_VALUE) ? 0 : existingLeaseCounter + 1;
    }
    
    public LeaderElectorListener.Event electLeader(boolean relinquish) {
        boolean wasLeaderAtStartOfElection = currentlyAmLeader;
        EventHelpers.ErrorEventsBuilder errorHolder = null;
        LeaderElectorListener.Event event = null;
        Instant startTime = Instant.now();

        try ( Connection connection = dataSource.getConnection()) {
            boolean originalAutoCommit = connection.getAutoCommit();
            connection.setAutoCommit(false);

            try ( PreparedStatement preparedStatement = sqlCmds.getSelectStmt(connection, myRoleId)) {
                preparedStatement.setQueryTimeout(configuration.getQueryTimeoutSecs());

                try ( ResultSet rs = preparedStatement.executeQuery()) {
                    event = executeInsideTableLock(connection, rs, wasLeaderAtStartOfElection, relinquish);
                }
                connection.commit();
                noOfConsecutiveTransientErrors = 0;
            } catch (SQLTransientException | SQLRecoverableException ex) {
                noOfConsecutiveTransientErrors++;
                if (noOfConsecutiveTransientErrors == 3) {
                    noOfConsecutiveTransientErrors = 0;
                    errorHolder = addError(errorHolder, new LeaderElectorExceptionNonRecoverable(ex));
                } else {
                    errorHolder = addError(errorHolder, new LeaderElectorExceptionRecoverable(ex));
                }
            } catch (Exception ex) {
                errorHolder = addError(errorHolder, new LeaderElectorExceptionNonRecoverable(ex));
                try {
                    connection.rollback();
                } catch (SQLException ex2) {
                    errorHolder = addError(errorHolder, new LeaderElectorExceptionNonRecoverable("Error performing rollback", ex2));
                }
            } finally {
                try {
                    connection.setAutoCommit(originalAutoCommit);
                } catch (SQLException ex) {
                    errorHolder = addError(errorHolder, new LeaderElectorExceptionNonRecoverable("Error resetting auto-commit", ex));
                }
            }
        } catch (SQLTransientException ex) {
            if (hasHadSuccessfulExection) {
                errorHolder = addError(errorHolder, new LeaderElectorExceptionRecoverable("No longer able to connect to database", ex));
            } else {
                errorHolder = addError(errorHolder, new LeaderElectorExceptionNonRecoverable("Cannot connect to database (first time)", ex));
            }
        } catch (Exception ex) {
            errorHolder = addError(errorHolder, new LeaderElectorExceptionNonRecoverable(ex));
        }

        if (errorHolder != null) {
            currentlyAmLeader = false;
            return errorHolder.build(startTime, wasLeaderAtStartOfElection, myRoleId);
        } else if (event != null) {
            if (!hasHadSuccessfulExection) {
                hasHadSuccessfulExection = true;
            }
            return event;
        }

        throw new RuntimeException("Unexpected event. Neither 'event' nor 'errorHolder' has a value");
    }

    private LeaderElectorListener.Event executeInsideTableLock(
            Connection connection, 
            ResultSet rs, 
            boolean wasLeaderAtStartOfElection,
            boolean relinquish) throws SQLException, LeaderElectorExceptionNonRecoverable {
        Instant startTime = Instant.now();
        LeaderElectorListener.Event event = null;

        int rows = 0;

        while (rs.next()) {
            rows++;
            if (rows > 1) {
                throw new LeaderElectorExceptionNonRecoverable("Table " + tableNameDisplay + " has more than one row. This is unexpected. It must contain exactly one row.");
            }
            final RowInLockTable row = new RowInLockTable(rs, myCandidateId);
            final long lastSeenTimestampMillis = row.getLastSeenTimestampMillis();
            final long nowUTCMillis = row.getNowUTCMillis();
            final long leaseCounter = row.getLeaseCounter();
            final RowInLockTable.CurrentLeaderDbStatus currentLeader = row.getCurrentLeaderDbStatus();
            final long leaseAgeMillis = nowUTCMillis - lastSeenTimestampMillis;
            final boolean leaseExpired = (leaseAgeMillis >= configuration.getAssumeDeadMs());

            checkRowValidity(row);

            switch (currentLeader) {
                case ME: {
                    if (relinquish) {
                        relinquishLeadership(connection, myRoleId, myCandidateId);
                        currentlyAmLeader = false;
                        hasRelinquishedLeadership = true;
                        if (wasLeaderAtStartOfElection) {
                            event = EventHelpers.createLeadershipLostEvent(startTime, myRoleId, row.getNodeId(), lastSeenTimestampMillis, leaseCounter);
                        }
                    } else {
                        affirmLeadership(connection, myRoleId, myCandidateId);
                        event = EventHelpers.createLeadershipConfirmedEvent(startTime, myRoleId, myCandidateId, lastSeenTimestampMillis, leaseCounter);
                    }
                }
                break;
                case SOMEONE_ELSE:
                    if (!leaseExpired) {
                        hasRelinquishedLeadership = false;
                    } 
                case NOBODY: {
                    if (leaseExpired && (!hasRelinquishedLeadership)) {
                        long newLeaseCounter = getNewLeaseCounter(leaseCounter);
                        assumeLeadership(connection, myRoleId, myCandidateId, newLeaseCounter);
                        event = EventHelpers.createLeadershipAssumedEvent(startTime, myRoleId, row.getNodeId(), lastSeenTimestampMillis, newLeaseCounter);
                        if (!wasLeaderAtStartOfElection) {
                            currentlyAmLeader = true;
                        }
                    } else {
                        event = EventHelpers.createLeadershipNoOp(startTime, myRoleId, row.getNodeId(), lastSeenTimestampMillis, leaseCounter);
                    }
                }
                break;
                default:
                    throw new LeaderElectorExceptionNonRecoverable("Unexpected value for 'currentLeader' : " + currentLeader);
            }

            if (event == null) {
                event = EventHelpers.createLeadershipNoOp(startTime, myRoleId, row.getNodeId(), lastSeenTimestampMillis, leaseCounter);
            }
        }
        
        if (rows == 0) {
            throw new LeaderElectorExceptionNonRecoverable("No row for lock_id='" + myRoleId + "' in table " + tableNameDisplay);
        }
        
        return event;
    }
                    
    private void checkRowValidity(RowInLockTable row) throws LeaderElectorExceptionNonRecoverable {
        String prefix = "ERROR: Unexpected: Table " + tableNameDisplay + " with content " + row ;
        if (row.getCurrentLeaderDbStatus()== CurrentLeaderDbStatus.ME && (!currentlyAmLeader)) {
            throw new LeaderElectorExceptionNonRecoverable(prefix
                    + ", says current candidate is leader but 'currentlyAmLeader' is false. "
                    + "Possibly table content was altered by an unsolicated process.");
        }

        if (row.getCurrentLeaderDbStatus() != CurrentLeaderDbStatus.ME && (currentlyAmLeader)) {
            throw new LeaderElectorExceptionNonRecoverable(prefix
                    + ", says current \"" + row.getNodeId() + "\" is leader, not me, but 'currentlyAmLeader' is true. "
                    + "In effect leadership was stolen. "        
                    + "Possible cause is if current process has not kept its lease alive. Perhaps the process has been dormant? "
                    + "Another possible cause is if candidates do not use the same configuration values for their Leader Elector process "
                    + "(for example, they use different values for 'assumeDeadMs')"
            );
        }
        if (row.getCurrentLeaderDbStatus() == CurrentLeaderDbStatus.NOBODY && row.getLastSeenTimestampMillis() != NO_LEADER_LASTSEENTIMESTAMP_MS) {
            throw new LeaderElectorExceptionNonRecoverable(prefix
                    + ", is inconsistent. "
                    + "Possibly table content was altered by an unsolicated process.");
        }
    }
    
    private EventHelpers.ErrorEventsBuilder addError(final EventHelpers.ErrorEventsBuilder errorEventsBuilder, LeaderElectorException error) {
        EventHelpers.ErrorEventsBuilder e = (errorEventsBuilder == null) ? new EventHelpers.ErrorEventsBuilder() : errorEventsBuilder;
        e.add(error);
        return e;
    }
    
}

A Table#

Server Requirements

Component Requirement
CPU 2 Cores
RAM 4 GB

If you’d like, I can also:

  • convert more pages,
  • generate a script to batch‑convert AsciiDoc → Markdown,
  • or help you adjust Hugo shortcodes for the Book theme.

Just tell me what direction you want to take next.