Update jobs
This commit is contained in:
@@ -5,7 +5,7 @@ WORKDIR /app
|
|||||||
# Use the official Microsoft .NET SDK image to build the code.
|
# Use the official Microsoft .NET SDK image to build the code.
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
WORKDIR /buildapp
|
WORKDIR /buildapp
|
||||||
COPY ["/src/Managing.Workers.Api/Managing.Workers.Api.csproj", "Managing.Workers.Api/"]
|
COPY ["/src/Managing.Workers/Managing.Workers.csproj", "Managing.Workers/"]
|
||||||
COPY ["/src/Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
|
COPY ["/src/Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
|
||||||
COPY ["/src/Managing.Application/Managing.Application.csproj", "Managing.Application/"]
|
COPY ["/src/Managing.Application/Managing.Application.csproj", "Managing.Application/"]
|
||||||
COPY ["/src/Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]
|
COPY ["/src/Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]
|
||||||
@@ -17,15 +17,15 @@ COPY ["/src/Managing.Infrastructure.Exchanges/Managing.Infrastructure.Exchanges.
|
|||||||
COPY ["/src/Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj", "Managing.Infrastructure.Messengers/"]
|
COPY ["/src/Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj", "Managing.Infrastructure.Messengers/"]
|
||||||
COPY ["/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
|
COPY ["/src/Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
|
||||||
COPY ["/src/Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj", "Managing.Infrastructure.Web3/"]
|
COPY ["/src/Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj", "Managing.Infrastructure.Web3/"]
|
||||||
RUN dotnet restore "/buildapp/Managing.Workers.Api/Managing.Workers.Api.csproj"
|
RUN dotnet restore "/buildapp/Managing.Workers/Managing.Workers.csproj"
|
||||||
COPY . .
|
COPY . .
|
||||||
WORKDIR "/buildapp/src/Managing.Workers.Api"
|
WORKDIR "/buildapp/src/Managing.Workers"
|
||||||
RUN dotnet build "Managing.Workers.Api.csproj" -c Release -o /app/build
|
RUN dotnet build "Managing.Workers.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
FROM build AS publish
|
FROM build AS publish
|
||||||
RUN dotnet publish "Managing.Workers.Api.csproj" -c Release -o /app/publish
|
RUN dotnet publish "Managing.Workers.csproj" -c Release -o /app/publish
|
||||||
|
|
||||||
FROM base AS final
|
FROM base AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=publish /app/publish .
|
COPY --from=publish /app/publish .
|
||||||
ENTRYPOINT ["dotnet", "Managing.Workers.Api.dll"]
|
ENTRYPOINT ["dotnet", "Managing.Workers.dll"]
|
||||||
|
|||||||
1720
src/Managing.Infrastructure.Database/Migrations/20251108203747_RenameJobsTableToUppercase.Designer.cs
generated
Normal file
1720
src/Managing.Infrastructure.Database/Migrations/20251108203747_RenameJobsTableToUppercase.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Managing.Infrastructure.Databases.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class RenameJobsTableToUppercase : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_jobs_Users_UserId",
|
||||||
|
schema: "public",
|
||||||
|
table: "jobs");
|
||||||
|
|
||||||
|
migrationBuilder.DropPrimaryKey(
|
||||||
|
name: "PK_jobs",
|
||||||
|
schema: "public",
|
||||||
|
table: "jobs");
|
||||||
|
|
||||||
|
migrationBuilder.RenameTable(
|
||||||
|
name: "jobs",
|
||||||
|
schema: "public",
|
||||||
|
newName: "Jobs");
|
||||||
|
|
||||||
|
migrationBuilder.AddPrimaryKey(
|
||||||
|
name: "PK_Jobs",
|
||||||
|
table: "Jobs",
|
||||||
|
column: "Id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_Jobs_Users_UserId",
|
||||||
|
table: "Jobs",
|
||||||
|
column: "UserId",
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_Jobs_Users_UserId",
|
||||||
|
table: "Jobs");
|
||||||
|
|
||||||
|
migrationBuilder.DropPrimaryKey(
|
||||||
|
name: "PK_Jobs",
|
||||||
|
table: "Jobs");
|
||||||
|
|
||||||
|
migrationBuilder.EnsureSchema(
|
||||||
|
name: "public");
|
||||||
|
|
||||||
|
migrationBuilder.RenameTable(
|
||||||
|
name: "Jobs",
|
||||||
|
newName: "jobs",
|
||||||
|
newSchema: "public");
|
||||||
|
|
||||||
|
migrationBuilder.AddPrimaryKey(
|
||||||
|
name: "PK_jobs",
|
||||||
|
schema: "public",
|
||||||
|
table: "jobs",
|
||||||
|
column: "Id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_jobs_Users_UserId",
|
||||||
|
schema: "public",
|
||||||
|
table: "jobs",
|
||||||
|
column: "UserId",
|
||||||
|
principalTable: "Users",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.SetNull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -807,7 +807,7 @@ namespace Managing.Infrastructure.Databases.Migrations
|
|||||||
b.HasIndex("Status", "JobType", "Priority", "CreatedAt")
|
b.HasIndex("Status", "JobType", "Priority", "CreatedAt")
|
||||||
.HasDatabaseName("idx_status_jobtype_priority_created");
|
.HasDatabaseName("idx_status_jobtype_priority_created");
|
||||||
|
|
||||||
b.ToTable("jobs", "public");
|
b.ToTable("Jobs", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b =>
|
modelBuilder.Entity("Managing.Infrastructure.Databases.PostgreSql.Entities.MoneyManagementEntity", b =>
|
||||||
|
|||||||
@@ -269,6 +269,9 @@ public class ManagingDbContext : DbContext
|
|||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey(e => e.UserId)
|
.HasForeignKey(e => e.UserId)
|
||||||
.OnDelete(DeleteBehavior.SetNull);
|
.OnDelete(DeleteBehavior.SetNull);
|
||||||
|
|
||||||
|
// Explicitly set table name to "Jobs" (uppercase)
|
||||||
|
entity.ToTable("Jobs");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Configure Scenario entity
|
// Configure Scenario entity
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ using Managing.Application.Abstractions.Repositories;
|
|||||||
using Managing.Domain.Backtests;
|
using Managing.Domain.Backtests;
|
||||||
using Managing.Infrastructure.Databases.PostgreSql.Entities;
|
using Managing.Infrastructure.Databases.PostgreSql.Entities;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Npgsql;
|
||||||
|
using NpgsqlTypes;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
|
|
||||||
namespace Managing.Infrastructure.Databases.PostgreSql;
|
namespace Managing.Infrastructure.Databases.PostgreSql;
|
||||||
@@ -40,16 +43,26 @@ public class PostgreSqlJobRepository : IJobRepository
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Build SQL query with optional job type filter
|
// Build SQL query with optional job type filter
|
||||||
|
// Use raw ADO.NET to avoid EF Core wrapping the query (which breaks FOR UPDATE SKIP LOCKED)
|
||||||
var sql = @"
|
var sql = @"
|
||||||
SELECT * FROM ""Jobs""
|
SELECT ""Id"", ""BundleRequestId"", ""UserId"", ""Status"", ""JobType"", ""Priority"",
|
||||||
WHERE ""Status"" = {0}";
|
""ConfigJson"", ""StartDate"", ""EndDate"", ""ProgressPercentage"",
|
||||||
|
""AssignedWorkerId"", ""LastHeartbeat"", ""CreatedAt"", ""StartedAt"",
|
||||||
|
""CompletedAt"", ""ResultJson"", ""ErrorMessage"", ""RequestId"",
|
||||||
|
""GeneticRequestId"", ""RetryCount"", ""MaxRetries"", ""RetryAfter"",
|
||||||
|
""IsRetryable"", ""FailureCategory""
|
||||||
|
FROM ""Jobs""
|
||||||
|
WHERE ""Status"" = @status";
|
||||||
|
|
||||||
var parameters = new List<object> { (int)JobStatus.Pending };
|
var parameters = new List<NpgsqlParameter>
|
||||||
|
{
|
||||||
|
new NpgsqlParameter("status", NpgsqlDbType.Integer) { Value = (int)JobStatus.Pending }
|
||||||
|
};
|
||||||
|
|
||||||
if (jobType.HasValue)
|
if (jobType.HasValue)
|
||||||
{
|
{
|
||||||
sql += @" AND ""JobType"" = {1}";
|
sql += @" AND ""JobType"" = @jobType";
|
||||||
parameters.Add((int)jobType.Value);
|
parameters.Add(new NpgsqlParameter("jobType", NpgsqlDbType.Integer) { Value = (int)jobType.Value });
|
||||||
}
|
}
|
||||||
|
|
||||||
sql += @"
|
sql += @"
|
||||||
@@ -57,18 +70,61 @@ public class PostgreSqlJobRepository : IJobRepository
|
|||||||
LIMIT 1
|
LIMIT 1
|
||||||
FOR UPDATE SKIP LOCKED";
|
FOR UPDATE SKIP LOCKED";
|
||||||
|
|
||||||
// Use raw SQL with FromSqlRaw to get the next job with row-level locking
|
_logger.LogDebug("Claiming job with SQL: {Sql}, Parameters: Status={Status}, JobType={JobType}",
|
||||||
var job = await _context.Jobs
|
sql, (int)JobStatus.Pending, jobType.HasValue ? (int)jobType.Value : (int?)null);
|
||||||
.FromSqlRaw(sql, parameters.ToArray())
|
|
||||||
.FirstOrDefaultAsync();
|
// Execute raw SQL using ADO.NET to get the job with row-level locking
|
||||||
|
var connection = _context.Database.GetDbConnection();
|
||||||
|
await using var command = connection.CreateCommand();
|
||||||
|
command.Transaction = transaction.GetDbTransaction();
|
||||||
|
command.CommandText = sql;
|
||||||
|
command.Parameters.AddRange(parameters.ToArray());
|
||||||
|
|
||||||
|
JobEntity? job = null;
|
||||||
|
await using var reader = await command.ExecuteReaderAsync();
|
||||||
|
|
||||||
|
if (await reader.ReadAsync())
|
||||||
|
{
|
||||||
|
job = new JobEntity
|
||||||
|
{
|
||||||
|
Id = reader.GetGuid(reader.GetOrdinal("Id")),
|
||||||
|
BundleRequestId = reader.IsDBNull(reader.GetOrdinal("BundleRequestId")) ? null : reader.GetGuid(reader.GetOrdinal("BundleRequestId")),
|
||||||
|
UserId = reader.GetInt32(reader.GetOrdinal("UserId")),
|
||||||
|
Status = reader.GetInt32(reader.GetOrdinal("Status")),
|
||||||
|
JobType = reader.GetInt32(reader.GetOrdinal("JobType")),
|
||||||
|
Priority = reader.GetInt32(reader.GetOrdinal("Priority")),
|
||||||
|
ConfigJson = reader.GetString(reader.GetOrdinal("ConfigJson")),
|
||||||
|
StartDate = reader.GetDateTime(reader.GetOrdinal("StartDate")),
|
||||||
|
EndDate = reader.GetDateTime(reader.GetOrdinal("EndDate")),
|
||||||
|
ProgressPercentage = reader.GetInt32(reader.GetOrdinal("ProgressPercentage")),
|
||||||
|
AssignedWorkerId = reader.IsDBNull(reader.GetOrdinal("AssignedWorkerId")) ? null : reader.GetString(reader.GetOrdinal("AssignedWorkerId")),
|
||||||
|
LastHeartbeat = reader.IsDBNull(reader.GetOrdinal("LastHeartbeat")) ? null : reader.GetDateTime(reader.GetOrdinal("LastHeartbeat")),
|
||||||
|
CreatedAt = reader.GetDateTime(reader.GetOrdinal("CreatedAt")),
|
||||||
|
StartedAt = reader.IsDBNull(reader.GetOrdinal("StartedAt")) ? null : reader.GetDateTime(reader.GetOrdinal("StartedAt")),
|
||||||
|
CompletedAt = reader.IsDBNull(reader.GetOrdinal("CompletedAt")) ? null : reader.GetDateTime(reader.GetOrdinal("CompletedAt")),
|
||||||
|
ResultJson = reader.IsDBNull(reader.GetOrdinal("ResultJson")) ? null : reader.GetString(reader.GetOrdinal("ResultJson")),
|
||||||
|
ErrorMessage = reader.IsDBNull(reader.GetOrdinal("ErrorMessage")) ? null : reader.GetString(reader.GetOrdinal("ErrorMessage")),
|
||||||
|
RequestId = reader.IsDBNull(reader.GetOrdinal("RequestId")) ? null : reader.GetString(reader.GetOrdinal("RequestId")),
|
||||||
|
GeneticRequestId = reader.IsDBNull(reader.GetOrdinal("GeneticRequestId")) ? null : reader.GetString(reader.GetOrdinal("GeneticRequestId")),
|
||||||
|
RetryCount = reader.GetInt32(reader.GetOrdinal("RetryCount")),
|
||||||
|
MaxRetries = reader.GetInt32(reader.GetOrdinal("MaxRetries")),
|
||||||
|
RetryAfter = reader.IsDBNull(reader.GetOrdinal("RetryAfter")) ? null : reader.GetDateTime(reader.GetOrdinal("RetryAfter")),
|
||||||
|
IsRetryable = reader.GetBoolean(reader.GetOrdinal("IsRetryable")),
|
||||||
|
FailureCategory = reader.IsDBNull(reader.GetOrdinal("FailureCategory")) ? null : reader.GetInt32(reader.GetOrdinal("FailureCategory"))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await reader.CloseAsync();
|
||||||
|
|
||||||
if (job == null)
|
if (job == null)
|
||||||
{
|
{
|
||||||
await transaction.RollbackAsync();
|
_logger.LogDebug("No job found to claim for worker {WorkerId}", workerId);
|
||||||
|
await transaction.CommitAsync();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the job status atomically
|
// Attach and update the job entity
|
||||||
|
_context.Jobs.Attach(job);
|
||||||
job.Status = (int)JobStatus.Running;
|
job.Status = (int)JobStatus.Running;
|
||||||
job.AssignedWorkerId = workerId;
|
job.AssignedWorkerId = workerId;
|
||||||
job.StartedAt = DateTime.UtcNow;
|
job.StartedAt = DateTime.UtcNow;
|
||||||
@@ -77,6 +133,7 @@ public class PostgreSqlJobRepository : IJobRepository
|
|||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
await transaction.CommitAsync();
|
await transaction.CommitAsync();
|
||||||
|
|
||||||
|
_logger.LogInformation("Claimed job {JobId} for worker {WorkerId}", job.Id, workerId);
|
||||||
return MapToDomain(job);
|
return MapToDomain(job);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -253,7 +310,7 @@ public class PostgreSqlJobRepository : IJobRepository
|
|||||||
|
|
||||||
var entities = await _context.Jobs
|
var entities = await _context.Jobs
|
||||||
.Where(j => j.Status == (int)JobStatus.Running &&
|
.Where(j => j.Status == (int)JobStatus.Running &&
|
||||||
(j.LastHeartbeat == null || j.LastHeartbeat < timeoutThreshold))
|
(j.LastHeartbeat == null || j.LastHeartbeat < timeoutThreshold))
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
return entities.Select(MapToDomain);
|
return entities.Select(MapToDomain);
|
||||||
@@ -267,7 +324,7 @@ public class PostgreSqlJobRepository : IJobRepository
|
|||||||
var staleJobs = await _context.Jobs
|
var staleJobs = await _context.Jobs
|
||||||
.AsTracking()
|
.AsTracking()
|
||||||
.Where(j => j.Status == (int)JobStatus.Running &&
|
.Where(j => j.Status == (int)JobStatus.Running &&
|
||||||
(j.LastHeartbeat == null || j.LastHeartbeat < timeoutThreshold))
|
(j.LastHeartbeat == null || j.LastHeartbeat < timeoutThreshold))
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
foreach (var job in staleJobs)
|
foreach (var job in staleJobs)
|
||||||
@@ -496,4 +553,3 @@ public class PostgreSqlJobRepository : IJobRepository
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
|||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
# Copy project files for dependency restoration
|
# Copy project files for dependency restoration
|
||||||
COPY ["Managing.Workers.Api/Managing.Workers.Api.csproj", "Managing.Workers.Api/"]
|
COPY ["Managing.Workers/Managing.Workers.csproj", "Managing.Workers/"]
|
||||||
COPY ["Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
|
COPY ["Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
|
||||||
COPY ["Managing.Application/Managing.Application.csproj", "Managing.Application/"]
|
COPY ["Managing.Application/Managing.Application.csproj", "Managing.Application/"]
|
||||||
COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]
|
COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]
|
||||||
@@ -21,19 +21,19 @@ COPY ["Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj",
|
|||||||
COPY ["Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj", "Managing.Infrastructure.Web3/"]
|
COPY ["Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj", "Managing.Infrastructure.Web3/"]
|
||||||
|
|
||||||
# Restore dependencies for all projects
|
# Restore dependencies for all projects
|
||||||
RUN dotnet restore "Managing.Workers.Api/Managing.Workers.Api.csproj"
|
RUN dotnet restore "Managing.Workers/Managing.Workers.csproj"
|
||||||
|
|
||||||
# Copy everything else and build
|
# Copy everything else and build
|
||||||
COPY . .
|
COPY . .
|
||||||
WORKDIR "/src/Managing.Workers.Api"
|
WORKDIR "/src/Managing.Workers"
|
||||||
RUN dotnet build "Managing.Workers.Api.csproj" -c Release -o /app/build
|
RUN dotnet build "Managing.Workers.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
FROM build AS publish
|
FROM build AS publish
|
||||||
RUN dotnet publish "Managing.Workers.Api.csproj" -c Release -o /app/publish
|
RUN dotnet publish "Managing.Workers.csproj" -c Release -o /app/publish
|
||||||
|
|
||||||
FROM base AS final
|
FROM base AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=publish /app/publish .
|
COPY --from=publish /app/publish .
|
||||||
|
|
||||||
ENTRYPOINT ["dotnet", "Managing.Workers.Api.dll"]
|
ENTRYPOINT ["dotnet", "Managing.Workers.dll"]
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<UserSecretsId>dotnet-Managing.Workers.Api-ff3f3987-4da4-4140-9180-b84c9e07b25f</UserSecretsId>
|
<UserSecretsId>dotnet-Managing.Workers-ff3f3987-4da4-4140-9180-b84c9e07b25f</UserSecretsId>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -24,3 +24,4 @@
|
|||||||
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj"/>
|
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
using Managing.Application.Workers;
|
|
||||||
using Managing.Bootstrap;
|
|
||||||
using Managing.Common;
|
|
||||||
using Managing.Infrastructure.Databases.InfluxDb.Models;
|
|
||||||
using Managing.Infrastructure.Databases.PostgreSql;
|
|
||||||
using Managing.Infrastructure.Databases.PostgreSql.Configurations;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
var host = Host.CreateDefaultBuilder(args)
|
|
||||||
.ConfigureAppConfiguration((hostingContext, config) =>
|
|
||||||
{
|
|
||||||
config.SetBasePath(AppContext.BaseDirectory);
|
|
||||||
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
|
|
||||||
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", optional: true)
|
|
||||||
.AddJsonFile("appsettings.SandboxLocal.json", optional: true, reloadOnChange: true)
|
|
||||||
.AddJsonFile("appsettings.ProductionLocal.json", optional: true, reloadOnChange: true)
|
|
||||||
.AddEnvironmentVariables()
|
|
||||||
.AddUserSecrets<Program>();
|
|
||||||
})
|
|
||||||
.ConfigureServices((hostContext, services) =>
|
|
||||||
{
|
|
||||||
var configuration = hostContext.Configuration;
|
|
||||||
|
|
||||||
// Initialize Sentry
|
|
||||||
SentrySdk.Init(options =>
|
|
||||||
{
|
|
||||||
options.Dsn = configuration["Sentry:Dsn"];
|
|
||||||
options.Debug = false;
|
|
||||||
options.SendDefaultPii = true;
|
|
||||||
options.AutoSessionTracking = true;
|
|
||||||
options.IsGlobalModeEnabled = false;
|
|
||||||
options.TracesSampleRate = 0.1;
|
|
||||||
options.Environment = hostContext.HostingEnvironment.EnvironmentName;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure database
|
|
||||||
var postgreSqlConnectionString = configuration.GetSection(Constants.Databases.PostgreSql)["ConnectionString"];
|
|
||||||
|
|
||||||
services.Configure<PostgreSqlSettings>(configuration.GetSection(Constants.Databases.PostgreSql));
|
|
||||||
services.Configure<InfluxDbSettings>(configuration.GetSection(Constants.Databases.InfluxDb));
|
|
||||||
|
|
||||||
// Add DbContext
|
|
||||||
services.AddDbContext<ManagingDbContext>((serviceProvider, options) =>
|
|
||||||
{
|
|
||||||
options.UseNpgsql(postgreSqlConnectionString, npgsqlOptions =>
|
|
||||||
{
|
|
||||||
npgsqlOptions.CommandTimeout(60);
|
|
||||||
npgsqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10), errorCodesToAdd: null);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hostContext.HostingEnvironment.IsDevelopment())
|
|
||||||
{
|
|
||||||
options.EnableDetailedErrors();
|
|
||||||
options.EnableSensitiveDataLogging();
|
|
||||||
options.LogTo(Console.WriteLine, LogLevel.Information); // Enable SQL logging to debug table name issues
|
|
||||||
}
|
|
||||||
|
|
||||||
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
|
|
||||||
options.EnableServiceProviderCaching();
|
|
||||||
}, ServiceLifetime.Scoped);
|
|
||||||
|
|
||||||
// Register compute dependencies (no Orleans)
|
|
||||||
services.RegisterComputeDependencies(configuration);
|
|
||||||
|
|
||||||
// Configure BacktestComputeWorker options
|
|
||||||
services.Configure<BacktestComputeWorkerOptions>(
|
|
||||||
configuration.GetSection(BacktestComputeWorkerOptions.SectionName));
|
|
||||||
|
|
||||||
// Get task slot from CapRover ({{.Task.Slot}}) or environment variable
|
|
||||||
// This identifies which instance of the worker is running
|
|
||||||
var taskSlot = Environment.GetEnvironmentVariable("TASK_SLOT") ??
|
|
||||||
Environment.GetEnvironmentVariable("CAPROVER_TASK_SLOT") ??
|
|
||||||
"0";
|
|
||||||
|
|
||||||
// Override WorkerId from environment variable if provided, otherwise use task slot
|
|
||||||
var workerId = Environment.GetEnvironmentVariable("WORKER_ID") ??
|
|
||||||
configuration["BacktestComputeWorker:WorkerId"] ??
|
|
||||||
$"{Environment.MachineName}-{taskSlot}";
|
|
||||||
services.Configure<BacktestComputeWorkerOptions>(options =>
|
|
||||||
{
|
|
||||||
options.WorkerId = workerId;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Configure GeneticComputeWorker options
|
|
||||||
services.Configure<GeneticComputeWorkerOptions>(
|
|
||||||
configuration.GetSection(GeneticComputeWorkerOptions.SectionName));
|
|
||||||
|
|
||||||
// Override Genetic WorkerId from environment variable if provided, otherwise use task slot
|
|
||||||
var geneticWorkerId = Environment.GetEnvironmentVariable("GENETIC_WORKER_ID") ??
|
|
||||||
configuration["GeneticComputeWorker:WorkerId"] ??
|
|
||||||
$"{Environment.MachineName}-genetic-{taskSlot}";
|
|
||||||
services.Configure<GeneticComputeWorkerOptions>(options =>
|
|
||||||
{
|
|
||||||
options.WorkerId = geneticWorkerId;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register the backtest compute worker if enabled
|
|
||||||
var isBacktestWorkerEnabled = configuration.GetValue<bool>("WorkerBacktestCompute", false);
|
|
||||||
if (isBacktestWorkerEnabled)
|
|
||||||
{
|
|
||||||
services.AddHostedService<BacktestComputeWorker>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the genetic compute worker if enabled
|
|
||||||
var isGeneticWorkerEnabled = configuration.GetValue<bool>("WorkerGeneticCompute", false);
|
|
||||||
if (isGeneticWorkerEnabled)
|
|
||||||
{
|
|
||||||
services.AddHostedService<GeneticComputeWorker>();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.ConfigureLogging((hostingContext, logging) =>
|
|
||||||
{
|
|
||||||
logging.ClearProviders();
|
|
||||||
logging.AddConsole();
|
|
||||||
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
|
|
||||||
})
|
|
||||||
.Build();
|
|
||||||
|
|
||||||
// Log worker status
|
|
||||||
var logger = host.Services.GetRequiredService<ILogger<Program>>();
|
|
||||||
var config = host.Services.GetRequiredService<IConfiguration>();
|
|
||||||
|
|
||||||
var isBacktestWorkerEnabled = config.GetValue<bool>("WorkerBacktestCompute", false);
|
|
||||||
var isGeneticWorkerEnabled = config.GetValue<bool>("WorkerGeneticCompute", false);
|
|
||||||
|
|
||||||
if (isBacktestWorkerEnabled)
|
|
||||||
{
|
|
||||||
var taskSlot = Environment.GetEnvironmentVariable("TASK_SLOT") ??
|
|
||||||
Environment.GetEnvironmentVariable("CAPROVER_TASK_SLOT") ??
|
|
||||||
"0";
|
|
||||||
var backtestWorkerId = Environment.GetEnvironmentVariable("WORKER_ID") ??
|
|
||||||
config["BacktestComputeWorker:WorkerId"] ??
|
|
||||||
$"{Environment.MachineName}-{taskSlot}";
|
|
||||||
logger.LogInformation("BacktestComputeWorker is enabled and will be started.");
|
|
||||||
logger.LogInformation("Backtest Worker ID: {WorkerId} (Task Slot: {TaskSlot})", backtestWorkerId, taskSlot);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogWarning("BacktestComputeWorker is disabled via configuration. No backtest jobs will be processed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isGeneticWorkerEnabled)
|
|
||||||
{
|
|
||||||
var taskSlot = Environment.GetEnvironmentVariable("TASK_SLOT") ??
|
|
||||||
Environment.GetEnvironmentVariable("CAPROVER_TASK_SLOT") ??
|
|
||||||
"0";
|
|
||||||
var geneticWorkerId = Environment.GetEnvironmentVariable("GENETIC_WORKER_ID") ??
|
|
||||||
config["GeneticComputeWorker:WorkerId"] ??
|
|
||||||
$"{Environment.MachineName}-genetic-{taskSlot}";
|
|
||||||
logger.LogInformation("GeneticComputeWorker is enabled and will be started.");
|
|
||||||
logger.LogInformation("Genetic Worker ID: {WorkerId} (Task Slot: {TaskSlot})", geneticWorkerId, taskSlot);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogWarning("GeneticComputeWorker is disabled via configuration. No genetic jobs will be processed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await host.RunAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.LogCritical(ex, "Application terminated unexpectedly");
|
|
||||||
SentrySdk.CaptureException(ex);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
SentrySdk.FlushAsync(TimeSpan.FromSeconds(2)).Wait();
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Managing.Workers.Api;
|
namespace Managing.Workers;
|
||||||
|
|
||||||
public class Worker : BackgroundService
|
public class Worker : BackgroundService
|
||||||
{
|
{
|
||||||
|
|||||||
39
src/Managing.Workers/Dockerfile
Normal file
39
src/Managing.Workers/Dockerfile
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Use the official Microsoft .NET runtime as the base image (no ASP.NET needed for console worker)
|
||||||
|
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Use the official Microsoft .NET SDK image to build the code.
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
# Copy project files for dependency restoration
|
||||||
|
COPY ["Managing.Workers/Managing.Workers.csproj", "Managing.Workers/"]
|
||||||
|
COPY ["Managing.Bootstrap/Managing.Bootstrap.csproj", "Managing.Bootstrap/"]
|
||||||
|
COPY ["Managing.Application/Managing.Application.csproj", "Managing.Application/"]
|
||||||
|
COPY ["Managing.Application.Abstractions/Managing.Application.Abstractions.csproj", "Managing.Application.Abstractions/"]
|
||||||
|
COPY ["Managing.Common/Managing.Common.csproj", "Managing.Common/"]
|
||||||
|
COPY ["Managing.Core/Managing.Core.csproj", "Managing.Core/"]
|
||||||
|
COPY ["Managing.Domain/Managing.Domain.csproj", "Managing.Domain/"]
|
||||||
|
COPY ["Managing.Infrastructure.Database/Managing.Infrastructure.Databases.csproj", "Managing.Infrastructure.Database/"]
|
||||||
|
COPY ["Managing.Infrastructure.Exchanges/Managing.Infrastructure.Exchanges.csproj", "Managing.Infrastructure.Exchanges/"]
|
||||||
|
COPY ["Managing.Infrastructure.Messengers/Managing.Infrastructure.Messengers.csproj", "Managing.Infrastructure.Messengers/"]
|
||||||
|
COPY ["Managing.Infrastructure.Storage/Managing.Infrastructure.Storage.csproj", "Managing.Infrastructure.Storage/"]
|
||||||
|
COPY ["Managing.Infrastructure.Web3/Managing.Infrastructure.Evm.csproj", "Managing.Infrastructure.Web3/"]
|
||||||
|
|
||||||
|
# Restore dependencies for all projects
|
||||||
|
RUN dotnet restore "Managing.Workers/Managing.Workers.csproj"
|
||||||
|
|
||||||
|
# Copy everything else and build
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/Managing.Workers"
|
||||||
|
RUN dotnet build "Managing.Workers.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "Managing.Workers.csproj" -c Release -o /app/publish
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
|
||||||
|
ENTRYPOINT ["dotnet", "Managing.Workers.dll"]
|
||||||
|
|
||||||
27
src/Managing.Workers/Managing.Workers.csproj
Normal file
27
src/Managing.Workers/Managing.Workers.csproj
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Worker">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>dotnet-Managing.Workers-ff3f3987-4da4-4140-9180-b84c9e07b25f</UserSecretsId>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1"/>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.11"/>
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.10"/>
|
||||||
|
<PackageReference Include="Sentry" Version="5.5.1"/>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Managing.Bootstrap\Managing.Bootstrap.csproj"/>
|
||||||
|
<ProjectReference Include="..\Managing.Application\Managing.Application.csproj"/>
|
||||||
|
<ProjectReference Include="..\Managing.Application.Abstractions\Managing.Application.Abstractions.csproj"/>
|
||||||
|
<ProjectReference Include="..\Managing.Infrastructure.Database\Managing.Infrastructure.Databases.csproj"/>
|
||||||
|
<ProjectReference Include="..\Managing.Infrastructure.Exchanges\Managing.Infrastructure.Exchanges.csproj"/>
|
||||||
|
<ProjectReference Include="..\Managing.Common\Managing.Common.csproj"/>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
|
|
||||||
295
src/Managing.Workers/Program.cs
Normal file
295
src/Managing.Workers/Program.cs
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
using Managing.Application.Workers;
|
||||||
|
using Managing.Bootstrap;
|
||||||
|
using Managing.Common;
|
||||||
|
using Managing.Infrastructure.Databases.InfluxDb.Models;
|
||||||
|
using Managing.Infrastructure.Databases.PostgreSql;
|
||||||
|
using Managing.Infrastructure.Databases.PostgreSql.Configurations;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
// Explicitly set the environment before creating the host builder
|
||||||
|
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")
|
||||||
|
?? Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT")
|
||||||
|
?? "Development";
|
||||||
|
|
||||||
|
// Log environment detection before host creation
|
||||||
|
var dotnetEnv = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT");
|
||||||
|
var aspnetcoreEnv = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
|
||||||
|
|
||||||
|
Console.WriteLine("═══════════════════════════════════════════════════════════");
|
||||||
|
Console.WriteLine("🔧 Environment Configuration");
|
||||||
|
Console.WriteLine("═══════════════════════════════════════════════════════════");
|
||||||
|
Console.WriteLine($" DOTNET_ENVIRONMENT: {dotnetEnv ?? "(not set)"}");
|
||||||
|
Console.WriteLine($" ASPNETCORE_ENVIRONMENT: {aspnetcoreEnv ?? "(not set)"}");
|
||||||
|
Console.WriteLine($" Selected Environment: {environment}");
|
||||||
|
Console.WriteLine("═══════════════════════════════════════════════════════════");
|
||||||
|
|
||||||
|
var host = Host.CreateDefaultBuilder(args)
|
||||||
|
.UseEnvironment(environment) // Explicitly set the environment
|
||||||
|
.ConfigureAppConfiguration((hostingContext, config) =>
|
||||||
|
{
|
||||||
|
var detectedEnv = hostingContext.HostingEnvironment.EnvironmentName;
|
||||||
|
Console.WriteLine($" Detected Environment: {detectedEnv}");
|
||||||
|
|
||||||
|
if (detectedEnv != environment)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" ⚠️ WARNING: Environment mismatch! Expected: {environment}, Got: {detectedEnv}");
|
||||||
|
}
|
||||||
|
|
||||||
|
config.SetBasePath(AppContext.BaseDirectory);
|
||||||
|
|
||||||
|
// Load configuration files in order (later files override earlier ones)
|
||||||
|
// 1. Base appsettings.json (always loaded)
|
||||||
|
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
|
||||||
|
Console.WriteLine(" ✓ Loaded: appsettings.json");
|
||||||
|
|
||||||
|
// 2. Load ONLY the environment-specific file (not other environments)
|
||||||
|
if (!string.IsNullOrEmpty(detectedEnv))
|
||||||
|
{
|
||||||
|
var envFile = $"appsettings.{detectedEnv}.json";
|
||||||
|
config.AddJsonFile(envFile, optional: true, reloadOnChange: true);
|
||||||
|
Console.WriteLine($" ✓ Loaded: {envFile} (optional)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Environment variables and user secrets (highest priority)
|
||||||
|
config.AddEnvironmentVariables()
|
||||||
|
.AddUserSecrets<Program>();
|
||||||
|
|
||||||
|
Console.WriteLine("═══════════════════════════════════════════════════════════");
|
||||||
|
})
|
||||||
|
.ConfigureServices((hostContext, services) =>
|
||||||
|
{
|
||||||
|
var configuration = hostContext.Configuration;
|
||||||
|
|
||||||
|
// Initialize Sentry
|
||||||
|
SentrySdk.Init(options =>
|
||||||
|
{
|
||||||
|
options.Dsn = configuration["Sentry:Dsn"];
|
||||||
|
options.Debug = false;
|
||||||
|
options.SendDefaultPii = true;
|
||||||
|
options.AutoSessionTracking = true;
|
||||||
|
options.IsGlobalModeEnabled = false;
|
||||||
|
options.TracesSampleRate = 0.1;
|
||||||
|
options.Environment = hostContext.HostingEnvironment.EnvironmentName;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure database
|
||||||
|
var postgreSqlConnectionString = configuration.GetSection(Constants.Databases.PostgreSql)["ConnectionString"];
|
||||||
|
|
||||||
|
// Log database connection details (mask password for security)
|
||||||
|
if (!string.IsNullOrEmpty(postgreSqlConnectionString))
|
||||||
|
{
|
||||||
|
var connectionParts = postgreSqlConnectionString.Split(';')
|
||||||
|
.Where(p => !string.IsNullOrWhiteSpace(p))
|
||||||
|
.Select(p => p.Trim())
|
||||||
|
.ToDictionary(
|
||||||
|
p => p.Split('=')[0].Trim(),
|
||||||
|
p => p.Contains('=') ? p.Substring(p.IndexOf('=') + 1).Trim() : string.Empty,
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var host = connectionParts.GetValueOrDefault("Host", "unknown");
|
||||||
|
var port = connectionParts.GetValueOrDefault("Port", "unknown");
|
||||||
|
var database = connectionParts.GetValueOrDefault("Database", "unknown");
|
||||||
|
var username = connectionParts.GetValueOrDefault("Username", "unknown");
|
||||||
|
var maskedConnectionString = postgreSqlConnectionString
|
||||||
|
.Replace(connectionParts.GetValueOrDefault("Password", ""), "***", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
Console.WriteLine("═══════════════════════════════════════════════════════════");
|
||||||
|
Console.WriteLine("📊 PostgreSQL Database Configuration");
|
||||||
|
Console.WriteLine("═══════════════════════════════════════════════════════════");
|
||||||
|
Console.WriteLine($" Host: {host}");
|
||||||
|
Console.WriteLine($" Port: {port}");
|
||||||
|
Console.WriteLine($" Database: {database}");
|
||||||
|
Console.WriteLine($" Username: {username}");
|
||||||
|
Console.WriteLine($" Connection String: {maskedConnectionString}");
|
||||||
|
Console.WriteLine("═══════════════════════════════════════════════════════════");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("⚠️ WARNING: PostgreSQL connection string is empty or not configured!");
|
||||||
|
}
|
||||||
|
|
||||||
|
services.Configure<PostgreSqlSettings>(configuration.GetSection(Constants.Databases.PostgreSql));
|
||||||
|
services.Configure<InfluxDbSettings>(configuration.GetSection(Constants.Databases.InfluxDb));
|
||||||
|
|
||||||
|
// Add DbContext
|
||||||
|
services.AddDbContext<ManagingDbContext>((serviceProvider, options) =>
|
||||||
|
{
|
||||||
|
options.UseNpgsql(postgreSqlConnectionString, npgsqlOptions =>
|
||||||
|
{
|
||||||
|
npgsqlOptions.CommandTimeout(60);
|
||||||
|
npgsqlOptions.EnableRetryOnFailure(maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10), errorCodesToAdd: null);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hostContext.HostingEnvironment.IsDevelopment())
|
||||||
|
{
|
||||||
|
options.EnableDetailedErrors();
|
||||||
|
options.EnableSensitiveDataLogging();
|
||||||
|
// SQL logging disabled - uncomment below line if needed for debugging
|
||||||
|
// options.LogTo(Console.WriteLine, LogLevel.Information);
|
||||||
|
}
|
||||||
|
|
||||||
|
options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
|
||||||
|
options.EnableServiceProviderCaching();
|
||||||
|
}, ServiceLifetime.Scoped);
|
||||||
|
|
||||||
|
// Register compute dependencies (no Orleans)
|
||||||
|
services.RegisterComputeDependencies(configuration);
|
||||||
|
|
||||||
|
// Configure BacktestComputeWorker options
|
||||||
|
services.Configure<BacktestComputeWorkerOptions>(
|
||||||
|
configuration.GetSection(BacktestComputeWorkerOptions.SectionName));
|
||||||
|
|
||||||
|
// Get task slot from CapRover ({{.Task.Slot}}) or environment variable
|
||||||
|
// This identifies which instance of the worker is running
|
||||||
|
var taskSlot = Environment.GetEnvironmentVariable("TASK_SLOT") ??
|
||||||
|
Environment.GetEnvironmentVariable("CAPROVER_TASK_SLOT") ??
|
||||||
|
"0";
|
||||||
|
|
||||||
|
// Override WorkerId from environment variable if provided, otherwise use task slot
|
||||||
|
var workerId = Environment.GetEnvironmentVariable("WORKER_ID") ??
|
||||||
|
configuration["BacktestComputeWorker:WorkerId"] ??
|
||||||
|
$"{Environment.MachineName}-{taskSlot}";
|
||||||
|
services.Configure<BacktestComputeWorkerOptions>(options =>
|
||||||
|
{
|
||||||
|
options.WorkerId = workerId;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure GeneticComputeWorker options
|
||||||
|
services.Configure<GeneticComputeWorkerOptions>(
|
||||||
|
configuration.GetSection(GeneticComputeWorkerOptions.SectionName));
|
||||||
|
|
||||||
|
// Override Genetic WorkerId from environment variable if provided, otherwise use task slot
|
||||||
|
var geneticWorkerId = Environment.GetEnvironmentVariable("GENETIC_WORKER_ID") ??
|
||||||
|
configuration["GeneticComputeWorker:WorkerId"] ??
|
||||||
|
$"{Environment.MachineName}-genetic-{taskSlot}";
|
||||||
|
services.Configure<GeneticComputeWorkerOptions>(options =>
|
||||||
|
{
|
||||||
|
options.WorkerId = geneticWorkerId;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register the backtest compute worker if enabled
|
||||||
|
var isBacktestWorkerEnabled = configuration.GetValue<bool>("WorkerBacktestCompute", false);
|
||||||
|
if (isBacktestWorkerEnabled)
|
||||||
|
{
|
||||||
|
services.AddHostedService<BacktestComputeWorker>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the genetic compute worker if enabled
|
||||||
|
var isGeneticWorkerEnabled = configuration.GetValue<bool>("WorkerGeneticCompute", false);
|
||||||
|
if (isGeneticWorkerEnabled)
|
||||||
|
{
|
||||||
|
services.AddHostedService<GeneticComputeWorker>();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ConfigureLogging((hostingContext, logging) =>
|
||||||
|
{
|
||||||
|
logging.ClearProviders();
|
||||||
|
logging.AddConsole();
|
||||||
|
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
|
||||||
|
})
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
// Log worker status
|
||||||
|
var logger = host.Services.GetRequiredService<ILogger<Program>>();
|
||||||
|
var config = host.Services.GetRequiredService<IConfiguration>();
|
||||||
|
|
||||||
|
// Log PostgreSQL connection details
|
||||||
|
var postgreSqlConnectionString = config.GetSection(Constants.Databases.PostgreSql)["ConnectionString"];
|
||||||
|
if (!string.IsNullOrEmpty(postgreSqlConnectionString))
|
||||||
|
{
|
||||||
|
var connectionParts = postgreSqlConnectionString.Split(';')
|
||||||
|
.Where(p => !string.IsNullOrWhiteSpace(p))
|
||||||
|
.Select(p => p.Trim())
|
||||||
|
.ToDictionary(
|
||||||
|
p => p.Split('=')[0].Trim(),
|
||||||
|
p => p.Contains('=') ? p.Substring(p.IndexOf('=') + 1).Trim() : string.Empty,
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
var hostName = connectionParts.GetValueOrDefault("Host", "unknown");
|
||||||
|
var port = connectionParts.GetValueOrDefault("Port", "unknown");
|
||||||
|
var database = connectionParts.GetValueOrDefault("Database", "unknown");
|
||||||
|
var username = connectionParts.GetValueOrDefault("Username", "unknown");
|
||||||
|
|
||||||
|
logger.LogInformation("═══════════════════════════════════════════════════════════");
|
||||||
|
logger.LogInformation("📊 PostgreSQL Database Configuration");
|
||||||
|
logger.LogInformation("═══════════════════════════════════════════════════════════");
|
||||||
|
logger.LogInformation(" Host: {Host}", hostName);
|
||||||
|
logger.LogInformation(" Port: {Port}", port);
|
||||||
|
logger.LogInformation(" Database: {Database}", database);
|
||||||
|
logger.LogInformation(" Username: {Username}", username);
|
||||||
|
logger.LogInformation("═══════════════════════════════════════════════════════════");
|
||||||
|
|
||||||
|
// Test database connection
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var scope = host.Services.CreateScope();
|
||||||
|
var dbContext = scope.ServiceProvider.GetRequiredService<ManagingDbContext>();
|
||||||
|
var canConnect = await dbContext.Database.CanConnectAsync();
|
||||||
|
if (canConnect)
|
||||||
|
{
|
||||||
|
logger.LogInformation("✅ Database connection test: SUCCESS");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogWarning("⚠️ Database connection test: FAILED - Cannot connect to database");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "❌ Database connection test: FAILED - {Error}", ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogWarning("⚠️ WARNING: PostgreSQL connection string is empty or not configured!");
|
||||||
|
}
|
||||||
|
|
||||||
|
var isBacktestWorkerEnabled = config.GetValue<bool>("WorkerBacktestCompute", false);
|
||||||
|
var isGeneticWorkerEnabled = config.GetValue<bool>("WorkerGeneticCompute", false);
|
||||||
|
|
||||||
|
if (isBacktestWorkerEnabled)
|
||||||
|
{
|
||||||
|
var taskSlot = Environment.GetEnvironmentVariable("TASK_SLOT") ??
|
||||||
|
Environment.GetEnvironmentVariable("CAPROVER_TASK_SLOT") ??
|
||||||
|
"0";
|
||||||
|
var backtestWorkerId = Environment.GetEnvironmentVariable("WORKER_ID") ??
|
||||||
|
config["BacktestComputeWorker:WorkerId"] ??
|
||||||
|
$"{Environment.MachineName}-{taskSlot}";
|
||||||
|
logger.LogInformation("BacktestComputeWorker is enabled and will be started.");
|
||||||
|
logger.LogInformation("Backtest Worker ID: {WorkerId} (Task Slot: {TaskSlot})", backtestWorkerId, taskSlot);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogWarning("BacktestComputeWorker is disabled via configuration. No backtest jobs will be processed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isGeneticWorkerEnabled)
|
||||||
|
{
|
||||||
|
var taskSlot = Environment.GetEnvironmentVariable("TASK_SLOT") ??
|
||||||
|
Environment.GetEnvironmentVariable("CAPROVER_TASK_SLOT") ??
|
||||||
|
"0";
|
||||||
|
var geneticWorkerId = Environment.GetEnvironmentVariable("GENETIC_WORKER_ID") ??
|
||||||
|
config["GeneticComputeWorker:WorkerId"] ??
|
||||||
|
$"{Environment.MachineName}-genetic-{taskSlot}";
|
||||||
|
logger.LogInformation("GeneticComputeWorker is enabled and will be started.");
|
||||||
|
logger.LogInformation("Genetic Worker ID: {WorkerId} (Task Slot: {TaskSlot})", geneticWorkerId, taskSlot);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogWarning("GeneticComputeWorker is disabled via configuration. No genetic jobs will be processed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await host.RunAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogCritical(ex, "Application terminated unexpectedly");
|
||||||
|
SentrySdk.CaptureException(ex);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SentrySdk.FlushAsync(TimeSpan.FromSeconds(2)).Wait();
|
||||||
|
}
|
||||||
24
src/Managing.Workers/Worker.cs
Normal file
24
src/Managing.Workers/Worker.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Managing.Workers;
|
||||||
|
|
||||||
|
public class Worker : BackgroundService
|
||||||
|
{
|
||||||
|
private readonly ILogger<Worker> _logger;
|
||||||
|
|
||||||
|
public Worker(ILogger<Worker> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||||
|
{
|
||||||
|
while (!stoppingToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
if (_logger.IsEnabled(LogLevel.Information))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(1000, stoppingToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
},
|
},
|
||||||
"WorkerBacktestCompute": true,
|
"WorkerBacktestCompute": true,
|
||||||
"BacktestComputeWorker": {
|
"BacktestComputeWorker": {
|
||||||
|
"WorkerId": "Oda-backtest-0",
|
||||||
"MaxConcurrentBacktests": 6,
|
"MaxConcurrentBacktests": 6,
|
||||||
"JobPollIntervalSeconds": 5,
|
"JobPollIntervalSeconds": 5,
|
||||||
"HeartbeatIntervalSeconds": 30,
|
"HeartbeatIntervalSeconds": 30,
|
||||||
@@ -19,14 +19,6 @@
|
|||||||
"HeartbeatIntervalSeconds": 30,
|
"HeartbeatIntervalSeconds": 30,
|
||||||
"StaleJobTimeoutMinutes": 10
|
"StaleJobTimeoutMinutes": 10
|
||||||
},
|
},
|
||||||
"PostgreSql": {
|
|
||||||
"ConnectionString": "Host=managing-postgre.apps.managing.live;Port=5432;Database=managing;Username=postgres;Password=29032b13a5bc4d37"
|
|
||||||
},
|
|
||||||
"InfluxDb": {
|
|
||||||
"Url": "https://influx-db.apps.managing.live",
|
|
||||||
"Organization": "managing-org",
|
|
||||||
"Token": "eOuXcXhH7CS13Iw4CTiDDpRjIjQtEVPOloD82pLPOejI4n0BsEj1YzUw0g3Cs1mdDG5m-RaxCavCMsVTtS5wIQ=="
|
|
||||||
},
|
|
||||||
"Sentry": {
|
"Sentry": {
|
||||||
"Dsn": "https://fe12add48c56419bbdfa86227c188e7a@glitch.kai.managing.live/1"
|
"Dsn": "https://fe12add48c56419bbdfa86227c188e7a@glitch.kai.managing.live/1"
|
||||||
},
|
},
|
||||||
@@ -68,7 +68,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Managing.Aspire.ServiceDefa
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Managing.Nswag", "Managing.Nswag\Managing.Nswag.csproj", "{BE50F950-C1D4-4CE0-B32E-6AAC996770D5}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Managing.Nswag", "Managing.Nswag\Managing.Nswag.csproj", "{BE50F950-C1D4-4CE0-B32E-6AAC996770D5}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Managing.Workers.Api", "Managing.Workers.Api\Managing.Workers.Api.csproj", "{B7D66A73-CA3A-4DE5-8E88-59D50C4018A6}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Managing.Workers", "Managing.Workers\Managing.Workers.csproj", "{B7D66A73-CA3A-4DE5-8E88-59D50C4018A6}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
|||||||
Reference in New Issue
Block a user