コンピューター/プログラムがクラッシュしたときに次のプログラムの開始時に問題を修正できるように、状態 + シリアル化されたクラス (または何か) を永続的に保存する方法を探しています。たとえば、ファイルをアップロードするときに、アップロードが完了していないかどうかを次回の起動時に知る必要があります。そうすれば、リモートファイルを削除できます(または、可能であれば追加するなど)。
ここで、基本的にデータベースを (簡単に交換するためのインターフェイスによって) 使用して、状態を格納するテーブル + 列 + 行を作成し、ジョブが正常に完了したときに行を削除する疑似コードを思いつきました。起動するたびに、データベースから読み取り、最後の状態に基づいて対応するアクションを呼び出すことができるようになりました。
問題点:
- 私がやっていることは、これを達成するために必要かどうかさえわかりません...代替案はありますか?
- これが正しい方法である場合、すでにこれを行っているライブラリが利用可能であることを期待していました(車輪を再発明する必要はありません)
- 依存性注入はどうですか?私のコードでは、単純にクラス名を行に追加していますが、これらすべてをプラグイン用に公開したいときに、クラス名の競合が発生した場合はどうすればよいでしょうか? 実際、保存されたデータを最初に渡す必要がある挿入された dll をどのように知ることができますか? (この時点でまだ意味があるかどうかはわかりません。)または、依存関係が削除された場合、削除されたプラグインに属しているデータベースファイル(ファイルを使用している場合)をどのように識別し、ユーザーに保持するように依頼できますかそれかどうか?
これまでのコード例:
class CPersistantthingy
{
IDatabase mDatabase;
string _mTablename;
List<string> _mCols = new List<string>(new string[]{"State","SerializedClass","ClassType"}); //can't contain "ID"
public Dictionary<string,string> AcquireRow()
{
//Make sure table exists and has the columns we need
CreateTableIfNotExist();
if(!VerifyTableHasColumns())
throw new CorruptedTableException();
//Create new row with State "undef", fetch the Auto Increment ID and return it
return mDatabase.CreateRow();
}
public void WriteRow(Dictionary<string,string> row)
{
mDatabase.UpdateRow(row); //The database will lookup by ID and update fields.
}
private void CreateTableIfNotExist()
{
mDatabase.CreateTableIfNotExist(_mTablename, _mCols);
}
private bool VerifyTableHasColumns()
{
return mDatabase.VerifyTableHasColumns(_mTablename, _mCols);
}
}
interface IDatabase
{
//CreateTable / CreateRow / UpdateRow etc etc
}
ファイルのアップロードの例:
States:
Acquired //Chosen a remote path
Started //First bytes written to server
Failed //Upload failed
したがって、行は次のようになります。
State: Acquired|Started|Failed
SerializedClass: {host: "127.0.0.1", user: "usr", local:"C:\somefile.txt", remote:"/we/somefile.txt"}
ClassType: CFileObj
したがって、私のプログラムでは、次のように使用できます。
SomeDatabaseClass_Object db = new SomeDatabaseClass_Object(blabla);
CPersistantthingy pers = new CPersistantthingy(db, "fileuploads");
private void StartUpload(string localfile)
{
var row = pers.AcquireRow();
row["State"] = "Acquired";
row["ClassType"] = "CFileObj";
/*Imagine that GetRemotePath reserves a remote path on the server we'll
upload our file to. That's how my current system works.*/
string remfile = GetRemotePath(localfile);
CFileObj obj = new CFileObj(currenthost, currentuser, localfile, remfile);
row["SerializedClass"] = obj.Serialize();
//Write the acquired state
pers.WriteRow(row);
//Uploading class has a callback to let us know when the first bytes have been written.
try
{
StartUpload(obj.local, obj.remote, () => { row["State"] = "Started"; pers.WriteRow(row); } );
}
catch(Exception ex)
{
row["State"] = "Failed";
pers.WriteRow(row);
throw; //Catch at caller to immediately fix rather than waiting for next boot.
}
//Now do whatever else you want to do on a succesful upload.
//Maybe add new states for this too so we know at least the upload succeeded.
//Finally delete the entry so it's not picked up at next boot.
pers.DeleteByKey(row["ID"]);
}
次に、クラッシュ後にサーバーが失敗したファイル (不完全なアップロード) を確実にクリーンアップするようにします。
public static void Main()
{
SomeDatabaseClass_Object db = new SomeDatabaseClass_Object(blabla);
CPersistantthingy pers = new CPersistantthingy(db, "fileuploads");
CUploadServerObj serverObj = new CUploadServerObj(bla,di,bla);
serverObj.Connect();
//Now let's imagine a class that hooks a state to an action etc.
var ima = new CImagineIt(pers);
/*I may have picked a bad example because I'd have to do the same for all States
but you get the idea. */
ima.AddStateAction("Failed", (row) => { FixFailedUpload(serverObj, pers, row); });
//Read all rows from database and fire actions accordingly
ima.DoWork();
}
アクションを使用すると、この場合、サーバー上のファイルがローカル ファイルよりも小さいかどうかを確認するだけです。
private void FixFailedUpload(CUploadServerObj serverObj, CPersistantthingy pers, Dictionary<string,string> row)
{
if(!row["ClassType"].Equals("CFileObj"))
{
//handle error
return;
}
CFileObj obj;
try
{
obj = DeSerialize(row["SerializedClass"]);
}//Catch and handle etc etc
//Are we using different credentials this boot? Then we can't check.
if(obj.host != currenthost || obj.usr != currentuser)
{
//handle error and return
}
try
{
if(serverObj.RemoteSizeSmaller(obj.local, obj.remote))
{
if(serverObj.DeleteFromRemote(obj.remote))
{
pers.DeleteByKey(row["ID"]);
}
}
}
catch(RemoteFileNotExistsException)
{
//The file didn't even exist so no worries
}
catch(RemoteFileDeleteException)
{
//The file existed but could not be removed.. it's probably time to request manual user input now because that's just weird.
}
}
これはうまくいくと思いますが、理想的ではありません。予約されたパスが別のプロセスによって既に書き込まれているために、アップロードが失敗した可能性があります。Acquired
つまり、状態を使用してファイルの削除を正当化することはできません。プログラムが 2 つの間でクラッシュする可能性があるため、事後に失敗の理由を確認しても役に立ちません。Acquired
とにかく立ち往生していることを意味します。または、最初に書き込まれたバイトとそのコールバックの間にクラッシュする可能性があります。つまり、Acquired
予約したファイル パスを取得したにもかかわらず、行き詰まるということです。