611 lines
20 KiB
C#
611 lines
20 KiB
C#
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using System.Reflection;
|
|
|
|
|
|
using NLog;
|
|
using Amazon.Runtime;
|
|
using Amazon.DynamoDBv2;
|
|
using Amazon.DynamoDBv2.Model;
|
|
using Amazon.DynamoDBv2.DataModel;
|
|
using Amazon.DynamoDBv2.DocumentModel;
|
|
using Amazon;
|
|
|
|
|
|
using ServerCore; using ServerBase;
|
|
|
|
|
|
using LOG_ACTION_TYPE = System.String;
|
|
|
|
|
|
namespace ServerBase;
|
|
|
|
//==============================================================================================
|
|
// 배치 쿼리를 전담 처리해 주는 추상 클래스 이다.
|
|
//==============================================================================================
|
|
|
|
public abstract partial class QueryBatchBase
|
|
{
|
|
public enum QueryResultType
|
|
{
|
|
Success = 0, // 쿼리를 성공 했다.
|
|
|
|
NotCalledQueryFunc = 1, // 쿼리 함수가 호출되지 않았다.
|
|
CalledQueryFunc = 2, // 쿼리 함수를 호출 했다.
|
|
QueryFailed = 3, // 쿼리를 실패 했다.
|
|
}
|
|
|
|
private DynamoDbClient m_dynamo_db_connector;
|
|
|
|
private IQueryRunner m_query_runner;
|
|
|
|
private bool m_is_use_transact = false;
|
|
private readonly string m_trans_id = string.Empty;
|
|
|
|
private UInt16 m_retry_delay_msec = 0;
|
|
private UInt16 m_retry_count = 0;
|
|
|
|
private readonly List<QueryExecutorBase> m_querys = new();
|
|
|
|
private IWithLogActor m_log_actor;
|
|
private readonly LogAction m_log_action;
|
|
private readonly List<ILogInvoker> m_business_logs = new();
|
|
|
|
public QueryBatchBase( IWithLogActor logActor, LOG_ACTION_TYPE logActionType
|
|
, DynamoDbClient dbConnector
|
|
, IQueryRunner queryRunner
|
|
, bool isUseTransact = true, string transId = ""
|
|
, UInt16 retryCount = 0, UInt16 retryDelayMSec = 0)
|
|
{
|
|
m_log_actor = logActor;
|
|
m_dynamo_db_connector = dbConnector;
|
|
m_query_runner = queryRunner;
|
|
|
|
m_is_use_transact = isUseTransact;
|
|
m_trans_id = string.IsNullOrEmpty(transId) ? Guid.NewGuid().ToString("N") : transId;
|
|
|
|
m_log_action = new LogAction(logActionType);
|
|
|
|
m_retry_count = retryCount;
|
|
m_retry_delay_msec = retryDelayMSec;
|
|
}
|
|
|
|
public QueryBatchBase( IWithLogActor logActor, LogAction logAction
|
|
, DynamoDbClient dbConnector
|
|
, IQueryRunner queryRunner
|
|
, bool isUseTransact = true, string transId = ""
|
|
, UInt16 retryCount = 0, UInt16 retryDelayMSec = 0)
|
|
{
|
|
m_log_actor = logActor;
|
|
m_dynamo_db_connector = dbConnector;
|
|
m_query_runner = queryRunner;
|
|
|
|
m_is_use_transact = isUseTransact;
|
|
m_trans_id = string.IsNullOrEmpty(transId) ? Guid.NewGuid().ToString("N") : transId;
|
|
|
|
m_log_action = logAction;
|
|
|
|
m_retry_count = retryCount;
|
|
m_retry_delay_msec = retryDelayMSec;
|
|
}
|
|
|
|
public bool addQuery( QueryExecutorBase queryContext
|
|
, QueryExecutorBase.FnCommit? fnCommit = null
|
|
, QueryExecutorBase.FnRollback? fnRollback = null )
|
|
{
|
|
queryContext.bindCallback(fnCommit, fnRollback);
|
|
queryContext.setQueryBatch(this);
|
|
|
|
if(false == queryContext.onAddQueryRequests())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_querys.Add(queryContext);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
//=========================================================================================
|
|
// DB 쿼리 주요 실행 함수들...
|
|
//=========================================================================================
|
|
|
|
public async Task<Result> prepareQueryWithStopwatch()
|
|
{
|
|
Stopwatch? stopwatch = null;
|
|
var event_tid = string.Empty;
|
|
|
|
var server_logic = ServerLogicApp.getServerLogicApp();
|
|
ArgumentNullException.ThrowIfNull(server_logic);
|
|
var server_config = server_logic.getServerConfig();
|
|
ArgumentNullException.ThrowIfNull(server_config);
|
|
|
|
if (true == server_config.PerformanceCheckEnable)
|
|
{
|
|
event_tid = string.IsNullOrEmpty(getTransId()) ? System.Guid.NewGuid().ToString("N") : getTransId();
|
|
stopwatch = Stopwatch.StartNew();
|
|
}
|
|
|
|
var result = await prepareQuery();
|
|
|
|
if (null != stopwatch)
|
|
{
|
|
var elapsed_msec = stopwatch.ElapsedMilliseconds;
|
|
stopwatch.Stop();
|
|
|
|
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
|
|
{
|
|
Log.getLogger().debug($"QueryBatch - prepareQuery Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}");
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<Result> prepareQuery()
|
|
{
|
|
var result = new Result();
|
|
|
|
foreach(var query in m_querys)
|
|
{
|
|
result = await query.onPrepareQuery();
|
|
if (result.isFail())
|
|
{
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task<Result> doQueryWithStopwatch()
|
|
{
|
|
Stopwatch? stopwatch = null;
|
|
var event_tid = string.Empty;
|
|
|
|
var server_logic = ServerLogicApp.getServerLogicApp();
|
|
ArgumentNullException.ThrowIfNull(server_logic);
|
|
var server_config = server_logic.getServerConfig();
|
|
ArgumentNullException.ThrowIfNull(server_config);
|
|
|
|
if (true == server_config.PerformanceCheckEnable)
|
|
{
|
|
event_tid = string.IsNullOrEmpty(getTransId()) ? System.Guid.NewGuid().ToString("N") : getTransId();
|
|
stopwatch = Stopwatch.StartNew();
|
|
}
|
|
|
|
var result = await doQuery();
|
|
|
|
if (null != stopwatch)
|
|
{
|
|
var elapsed_msec = stopwatch.ElapsedMilliseconds;
|
|
stopwatch.Stop();
|
|
|
|
if (elapsed_msec > Constant.STOPWATCH_LOG_LIMIT_MSEC)
|
|
{
|
|
Log.getLogger().debug($"QueryBatch - doQuery Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}");
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<Result> doQuery()
|
|
{
|
|
var result = new Result();
|
|
var err_msg = string.Empty;
|
|
|
|
try
|
|
{
|
|
foreach (var query in m_querys)
|
|
{
|
|
result = await query.onQuery();
|
|
if (result.isFail())
|
|
{
|
|
Log.getLogger().error(result.toBasicString());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch(System.Exception e)
|
|
{
|
|
err_msg = $"Exception !!!, doQuery(), Exception:{e} - {toBasicString()}";
|
|
result.setFail(ServerErrorCode.DynamoDbQueryException, err_msg);
|
|
Log.getLogger().error(result.toBasicString());
|
|
}
|
|
|
|
finally
|
|
{
|
|
if (result.isSuccess())
|
|
{
|
|
result = await commitQueryAllWithStopWatch();
|
|
}
|
|
else
|
|
{
|
|
await rollbackQueryAll(result);
|
|
}
|
|
|
|
onSendMsgByHandler(result);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//=====================================================================================
|
|
// 1. 등록된 쿼리들을 등록 순서대로 처리해 준다.
|
|
// 2. Non-Transaction 쿼리에 오류가 발생할 경우 이후 처리할 쿼리는 모두 실행하지 않는다.
|
|
// 3. TransactionGetItem 쿼리에 오류가 발생할 경우 TransactionGetItemRequest에
|
|
// 등록된 모든 쿼리는 처리되지 않는다.
|
|
// 4. TransactionWriteItem 쿼리에 오류가 발생할 경우 TransactionWriteItemRequest에
|
|
// 등록된 모든 쿼리는 처리되지 않는다.
|
|
//=====================================================================================
|
|
|
|
private async Task<Result> commitQueryAllWithStopWatch()
|
|
{
|
|
Stopwatch? stopwatch = null;
|
|
var event_tid = string.Empty;
|
|
|
|
var server_logic = ServerLogicApp.getServerLogicApp();
|
|
ArgumentNullException.ThrowIfNull(server_logic);
|
|
var server_config = server_logic.getServerConfig();
|
|
ArgumentNullException.ThrowIfNull(server_config);
|
|
|
|
if (true == server_config.PerformanceCheckEnable)
|
|
{
|
|
event_tid = string.IsNullOrEmpty(getTransId()) ? System.Guid.NewGuid().ToString("N") : getTransId();
|
|
stopwatch = Stopwatch.StartNew();
|
|
}
|
|
|
|
var result = await commitQueryAll();
|
|
|
|
if (null != stopwatch)
|
|
{
|
|
var elapsed_msec = stopwatch.ElapsedMilliseconds;
|
|
stopwatch.Stop();
|
|
|
|
if (elapsed_msec > 100)
|
|
{
|
|
Log.getLogger().debug($"QueryBatch - commitQueryAll Stopwatch Stop : ETID:{event_tid}, ElapsedMSec:{elapsed_msec}");
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<Result> commitQueryAll()
|
|
{
|
|
var result = new Result();
|
|
|
|
var try_count = 0;
|
|
|
|
while(true)
|
|
{
|
|
(result, bool is_retry_query) = await commitQueryRunner();
|
|
if (true == is_retry_query)
|
|
{
|
|
try_count++;
|
|
if (m_retry_count <= try_count)
|
|
{
|
|
break;
|
|
}
|
|
|
|
await Task.Delay(m_retry_delay_msec);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (result.isFail())
|
|
{
|
|
await rollbackQueryAll(result);
|
|
}
|
|
else
|
|
{
|
|
foreach (var query in m_querys)
|
|
{
|
|
if (QueryBatchBase.QueryResultType.NotCalledQueryFunc == await query.doFnCommit())
|
|
{
|
|
await query.onQueryResponseCommit();
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private async Task<(Result, bool)> commitQueryRunner()
|
|
{
|
|
var result = new Result();
|
|
var err_msg = string.Empty;
|
|
|
|
try
|
|
{
|
|
result = await onCommitQueryRunner();
|
|
}
|
|
catch (TransactionCanceledException e)
|
|
{
|
|
var error_code = ServerErrorCode.DynamoDbTransactionCanceledException;
|
|
err_msg = $"Failed to commitQueryRunner() !!!, Transaction Canceled, implies a client issue, fix before retrying !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
|
|
result.setFail(error_code, err_msg);
|
|
Log.getLogger().error(result.toBasicString());
|
|
|
|
(var dispatch_result, var exception_custom_result) = await onDispatchTransactionCancelException(e);
|
|
if (dispatch_result.isFail())
|
|
{
|
|
err_msg = $"Failed to onDispatchTransactionCancelException() !!! : {dispatch_result.toBasicString()}, {e.toExceptionString()} - {toBasicString()}";
|
|
Log.getLogger().error(err_msg);
|
|
}
|
|
else
|
|
{
|
|
// 재정의된 오류 코드가 설정된 경우
|
|
if (exception_custom_result.isFail())
|
|
{
|
|
// 재정의한 예외 결과를 dispatch 했다면 오류 로그로 설정해 준다. !!!
|
|
err_msg = $"QueryBatch.onDispatchTransactionCancelException() !!! : exceptionResult:{exception_custom_result.getResultString()} - {toBasicString()}";
|
|
result.setFail(exception_custom_result.getErrorCode(), exception_custom_result.getResultString());
|
|
Log.getLogger().error(result.toBasicString());
|
|
}
|
|
}
|
|
}
|
|
catch (TransactionConflictException e)
|
|
{
|
|
var error_code = ServerErrorCode.DynamoDbTransactionConflictException;
|
|
err_msg = $"TransactionConflictException !!!, Failed to perform in commitQueryRunner() !!!, Transaction conflict occurred !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
|
|
result.setFail(error_code, err_msg);
|
|
Log.getLogger().error(result.toBasicString());
|
|
}
|
|
catch (AmazonDynamoDBException e)
|
|
{
|
|
var error_code = ServerErrorCode.DynamoDbAmazonDynamoDbException;
|
|
err_msg = $"AmazonDynamoDBException !!!, Failed to perform in commitQueryRunner() !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
|
|
result.setFail(error_code, err_msg);
|
|
Log.getLogger().error(err_msg);
|
|
}
|
|
catch (AmazonServiceException e)
|
|
{
|
|
var error_code = ServerErrorCode.DynamoDbAmazonServiceException;
|
|
err_msg = $"AmazonServiceException !!!, Failed to perform in commitQueryRunner() !!! : errorCode:{error_code}, {e.toExceptionString()} - {toBasicString()}";
|
|
result.setFail(error_code, err_msg);
|
|
Log.getLogger().error(result.toBasicString());
|
|
|
|
return (result, true);
|
|
}
|
|
catch (System.Exception e)
|
|
{
|
|
var error_code = ServerErrorCode.TryCatchException;
|
|
err_msg = $"Exception !!!, Failed to perform in commitQueryRunner() !!! : errorCode:{error_code}, exception:{e} - {toBasicString()}";
|
|
result.setFail(error_code, err_msg);
|
|
Log.getLogger().error(result.toBasicString());
|
|
}
|
|
|
|
return (result, false);
|
|
}
|
|
|
|
private async Task rollbackQueryAll(Result errorResult)
|
|
{
|
|
await onRollbackQueryRunnner();
|
|
|
|
foreach (var query in m_querys)
|
|
{
|
|
if (QueryBatchBase.QueryResultType.NotCalledQueryFunc == await query.doFnRollback(errorResult))
|
|
{
|
|
await query.onQueryResponseRollback(errorResult);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected abstract Task<Result> onCommitQueryRunner();
|
|
|
|
protected abstract Task onRollbackQueryRunnner();
|
|
|
|
public virtual void onSendMsgByHandler(Result errorResult)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//==============================================================================================
|
|
// 배치쿼리중에서 특정 쿼리를 찾는다.
|
|
//==============================================================================================
|
|
public TQuery? getQuery<TQuery>()
|
|
where TQuery : QueryExecutorBase
|
|
{
|
|
return m_querys.Find(x => x is TQuery == true) as TQuery;
|
|
}
|
|
|
|
public List<TQuery> getQuerys<TQuery>()
|
|
where TQuery : QueryExecutorBase
|
|
{
|
|
var querys = new List<TQuery>();
|
|
foreach (var each in m_querys)
|
|
{
|
|
if (each is TQuery query)
|
|
{
|
|
querys.Add(query);
|
|
}
|
|
}
|
|
|
|
return querys;
|
|
}
|
|
|
|
public void appendBusinessLogs(List<ILogInvoker> logs)
|
|
{
|
|
foreach(var log in logs)
|
|
{
|
|
m_business_logs.Add(log);
|
|
}
|
|
}
|
|
|
|
public void appendBusinessLog(ILogInvoker log)
|
|
{
|
|
m_business_logs.Add(log);
|
|
}
|
|
|
|
private bool hasBusinessLog()
|
|
{
|
|
var log_actor = getLogActor();
|
|
|
|
if (null != log_actor && 0 < m_business_logs.Count)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void sendBusinessLog()
|
|
{
|
|
if (false == hasBusinessLog())
|
|
{
|
|
return;
|
|
}
|
|
|
|
BusinessLogger.collectLogs(m_log_action, m_log_actor, m_business_logs);
|
|
}
|
|
|
|
public string toBasicString()
|
|
{
|
|
return $"{this.getTypeName()}, LogActionType:{m_log_action.getLogActionType()}, TransId:{m_trans_id}";
|
|
}
|
|
|
|
protected abstract Task<(Result, Result)> onDispatchTransactionCancelException(TransactionCanceledException exception);
|
|
}//QueryBatchBase
|
|
|
|
|
|
//==============================================================================================
|
|
// 배치 쿼리를 전담 처리해 주는 제네릭 클래스 이다.
|
|
//==============================================================================================
|
|
public partial class QueryBatch<TQueryRunner> : QueryBatchBase
|
|
where TQueryRunner : IQueryRunner, new()
|
|
{
|
|
public QueryBatch( IWithLogActor logActor
|
|
, LOG_ACTION_TYPE logActionType
|
|
, DynamoDbClient dbClient
|
|
, bool isUseTransact = true, string transId = ""
|
|
, UInt16 retryCount = 3, UInt16 retryDelayMSec = 1000, string eventTid = "" )
|
|
: base( logActor, logActionType, dbClient, new TQueryRunner()
|
|
, isUseTransact, transId, retryCount, retryDelayMSec)
|
|
{
|
|
getQueryRunner().setQueryBatch(this);
|
|
}
|
|
|
|
public QueryBatch( IWithLogActor logActor
|
|
, LogAction logAction
|
|
, DynamoDbClient dbClient
|
|
, bool isUseTransact = true, string transId = ""
|
|
, UInt16 retryCount = 3, UInt16 retryDelayMSec = 1000, string eventTid = "" )
|
|
: base( logActor, logAction.getLogActionType(), dbClient, new TQueryRunner()
|
|
, isUseTransact, transId, retryCount, retryDelayMSec)
|
|
{
|
|
getQueryRunner().setQueryBatch(this);
|
|
}
|
|
|
|
protected override async Task<Result> onCommitQueryRunner()
|
|
{
|
|
var query_runner = getQueryRunner();
|
|
var result = await query_runner.onTryCommitQueryRunner();
|
|
if (result.isFail())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
return await getQueryRunner().onCommitQueryRunner();
|
|
}
|
|
|
|
protected override async Task onRollbackQueryRunnner()
|
|
{
|
|
await getQueryRunner().onRollbackQueryRunner();
|
|
}
|
|
|
|
protected override async Task<(Result, Result)> onDispatchTransactionCancelException(TransactionCanceledException exception)
|
|
{
|
|
await Task.CompletedTask;
|
|
|
|
var log_actor = getLogActor();
|
|
ArgumentNullException.ThrowIfNull(log_actor, $"log_actor is null !!!");
|
|
ArgumentNullException.ThrowIfNull(exception, $"exception is null !!! - {log_actor.getTypeName()}");
|
|
|
|
return await getQueryRunner().onHandleTransactionCancelException(exception);
|
|
}
|
|
}//QueryBatch<TQueryRunner>
|
|
|
|
|
|
//==============================================================================================
|
|
// 배치 쿼리 + 모든 쿼리 처리후 TMsg 를 결과로 전송해 주는 제네릭 클래스 이다.
|
|
//==============================================================================================
|
|
public class QueryBatch<TQueryRunner, TMsg> : QueryBatch<TQueryRunner>
|
|
where TQueryRunner : IQueryRunner, new()
|
|
where TMsg : class, new()
|
|
{
|
|
protected readonly TMsg m_reserved_msg ; // 전송하고자 하는 패킷
|
|
protected readonly object m_sender; // 전송객체 : setError(ErrorCode error), sendPacketAsync(TMsg msg) 맴버함수가 있어야 한다. !!!
|
|
|
|
public QueryBatch( IWithLogActor logActor
|
|
, LOG_ACTION_TYPE logActionType
|
|
, DynamoDbClient dbClient
|
|
, object sender
|
|
, TMsg? msg = null
|
|
, bool isUseTransact = true )
|
|
: base(logActor, logActionType, dbClient, isUseTransact )
|
|
{
|
|
if (msg == null)
|
|
{
|
|
m_reserved_msg = new TMsg();
|
|
}
|
|
else
|
|
{
|
|
m_reserved_msg = msg;
|
|
}
|
|
|
|
m_sender = sender;
|
|
}
|
|
|
|
public QueryBatch( IWithLogActor logActor
|
|
, LogAction logAction
|
|
, DynamoDbClient dbClient
|
|
, object sender
|
|
, TMsg? msg = null
|
|
, bool isUseTransact = true )
|
|
: base(logActor, logAction.getLogActionType(), dbClient, isUseTransact )
|
|
{
|
|
if (msg == null)
|
|
{
|
|
m_reserved_msg = new TMsg();
|
|
}
|
|
else
|
|
{
|
|
m_reserved_msg = msg;
|
|
}
|
|
|
|
m_sender = sender;
|
|
}
|
|
|
|
public TMsg Msg
|
|
{
|
|
get { return m_reserved_msg; }
|
|
}
|
|
|
|
private void setErrorToMsg(Result errorResult)
|
|
{
|
|
|
|
}
|
|
|
|
public override void onSendMsgByHandler(Result errorResult)
|
|
{
|
|
if (m_reserved_msg != null && m_sender != null)
|
|
{
|
|
setErrorToMsg(errorResult);
|
|
|
|
}
|
|
}
|
|
|
|
}//QueryBatch<TQueryRunner, TMsg>
|