UndoShadowCount, UndoShadowSizeという2つの変数を追加して解決

MemoryBlockCountとMemoryBlockSizeが実際と合わないという不具合がぶり返してしまった.カードを3点削除→UNDOSYSTEM:CommandEndで系統並び替えを実行する直前だ.totalcount 10165に対し,MemoryBlockCountは10218になっている.つまり,デクリメントされていない.カードを1点削除するだけで再現できる.カウントで24の差異が出る.CANとNringの合計とオブジェクト数=生成オブジェクト数ーリサイクル個数は一致している.カードを削除した場合,UNDOオブジェクトが生成されるので総数では増加しなくてはならない.従って,間違っているのはtotalcountの方である可能性がある.⇒UNDONODEは24個生成されてNringに入っている.nodecountが更新されていないのではないか?⇒いや,nodecountは最新だ.

ゴミ箱のカウントが+91, Nringが-24でMemoryBlockCountは+91.つまり,Nringからゴミ箱に移動した分がそっくり消えている.MemoryBlockCountはヒープから実メモリを取得したときにしかインクリメントされていないので,この差異がどこから発生しているのかよく分からない.⇒FAMILYTREE::DeleteCardDataの実行で91件の新規メモリ取得が発生している.同じ件数だけゴミ箱が増えているのだが,問題はNringの純減-24がどこに行ってしまったか?という点だ.

少なくともメモリからパージされていないのだから,ゴミ箱以外には往き場所はないのだが… Nringからパージされているノードは208件ある.これらはすべてゴミ箱に移動しているものと推定されるが,ゴミ箱はそこまで増加していない.ますます訳が分からなくなってきた… どうもこの統計はかなり怪しい.MemoryBlockCountをデクリメントしている関数が2つある.block::delmemと_delmemだ._delmemからdelmemを呼び出すようにして,1箇所で管理するようにしておこう.これでメモリ取得・解放の窓口はgetmem, delmemに1本化できた.

getmem/delmemはシステムで使用する全メモリを管理しているので,MemoryBlockCountとMemoryBlockSizeは全メモリに対応している.サイズ不定のフリーメモリは通常freeblockオブジェクトとして生成されるが,中には完全なフリーメモリとして扱われるものもある.UNDOのShadowイメージはその典型だ.というか,おそらくそれ以外では使われていないのではないかと思う.バッファなどはどうか?その辺りもそうなっているかもしれない… ⇒少なくともQUICKDBで使われているバッファはfreeblockで取得したものだ.

getmemで直接フリーメモリを取得している例として,COUPLING:SaveFamilyTreeで使っている人名/結婚リンクバックアップ領域というのがあるが,その関数内で解放されている.それ以外ではUNDOのShadowしかない.getmemではどこから呼び出されているか判別できないので,呼び出し元のUNDOSYSTEM::SetUndoListで管理するしかないだろう.暫定的にUndoShadowCount, UndoShadowSizeという2つの変数を作ってみた.⇒フリーメモリの取得はこれでよいとして,削除にどう対応するか?⇒UNDONODE::Disposeでfreeblock::delmemしている.ここで対処すればよい.⇒準備は整った.数字を見てみよう.

完全に一致した!これで問題は解決した.グローバルメモリの収支はこれで完全に押さえることができたと思う.UNDOの動作を確認してみよう.⇒ReferenceControlで参照カウントと参照リストの不一致が発生した.昨日はゴミ箱なしでこの不良が出ていたが,ゴミ箱ありでも同じ結果だ.UNDONODE::UndoRestoreで「オブジェクトの参照を一旦すべてクリアする」ということをやっている.ここでは対象ノードのすべてのスロットを検査して参照解除を実施しているが,スロット7,  8, 39と進んだところでエラーになっている.これはかなりおかしい.

スロット7, 8で問題なく処理できたのに39で突然不一致になるというのは考え難い※.障害ノードは#977 baselist 基本世代枠@33のCARDLINKでスロット39というのは,結婚ページ0に当たる.このカードは親ページを2, 結婚ページ1を持っている.ここで不一致が生じるというのはノーマルな動作なのではないかと思う.⇒いずれにしても参照リストと参照カウントの不一致は避けられない,UNDOに対応した処理が入っていないのだから… ⇒この問題は保留としておこう.

※これはちょっと勘違いしている.参照リストというのは被参照ノードが持っているリストで,参照元のスロットを連続検査しているが,対象の参照リストはそれぞれ独立のものだ.

▲参照リスト管理をUNDOと両立させる

ゴミ箱ありでカードを3枚削除→アプリ終了してfreeblock::delmemでエラーが発生した.引数のダブルポインタが空を指している.フロート状態のノードを削除しようとしているためだ.DoublePtrは親リンクが空の場合はNULLSPOTを返している.これはNULLが格納された場所を指している.freeblock::delmemでこのエラーが出るということは,この関数ではフロート状態のオブジェクトをdeleteできないということを意味する.これは仕様的にはやや問題があるが,実際問題として,ゴミ箱に入っているノードが親なしという状態自体がイレギュラーだ.

なぜこんなことが起こるのかについては,別途調べる必要がある.暫定的にNULLSPOTにこのノードのアドレスを格納して返すようにした.このアドレスはメモリをパージした後直ちにNULLに戻されるので,ここだけの話であれば,これでも悪くはないのだが…

CAN::ThrowCanを実行中,QNが負というエラーが発生した.printnamaeでバッファオーバーランが発生している.⇒修正した.

上記でfreeblock::_delmemの仕様を変更して,delmemを呼び出すように修正しているが,やはりこれでは通らない.これらの2つは用途が微妙に異なるので,それぞれ独立に併存させるしかない.

  1. freeblock::_delmemとdelmemはともにメモリ上の要素ブロックを解放するための関数で,事後にMemoryBlockCountとMemoryBlockSizeを更新する.また,これらの関数では要素のアドレスが格納されたポインタ(スロットないしベタ参照)を(可能な場合には)空にする.
  2. freeblock::delmemは汎用的なメモリ削除関数でfreeblockを含むnoduleクラスオブジェクトの他,完全なフリーメモリブロックも扱うことができるが,メモリブロックを参照するリンクに空を書き込む必要があるため,引数ではvoid**を取っている.
  3. この仕様では,フリーメモリを関数内部で(ローカル変数を使って)getmemして廃棄することはできないということになる.この制限はかなりきついかもしれない.厳格過ぎるかもしれないが,安全ではある.フリーメモリを解放するためには,それが「どこに置いてあるか?」を知らなくてはならない.
  4. freeblock::_delmemはnoduleクラスオブジェクトをメモリからパージするために用いる.noduleは通常メモリからパージされる時点ではフロート状態で所在不明(接続先不明)となっているため,delmemにダブルポインタの形式で渡すことができない.

もし,これらを統一して単一の関数でMemoryBlockCountとMemoryBlockSizeを管理したいというのであれば,delmemと_delmemの両側から呼び出されるフラットな関数を用意するしかない.実装は簡単なのでやってみよう.getmemに対応するフラットな関数を改めてdelmemとし,現行のdelmemはdelmem_とリネームする.delmemは外部から使えないようにprivateとした.

▲MemoryBlockCountの不整合がぶり返してしまった.カードを1点削除→アプリ終了して,totalcount 1 MemoryBlockCount 10, TotalSize 1180 MemoryBlockSize 27576となった.要素数で9個,サイズで26396のオブジェクトが紛失している.ThrowCan 前 diff=0 10302 = waste 10301 + Nodecount 1 =10302となっているので,NringにはCANしか残っていない.残りはすべてゴミ箱の中とUNDOのShadowだけだ.NringTotal 1180+CanTotal 7023434+UndoShadowSize 7024614は,MemoryBlockSize 7024614と一致している.

ThrowCan 後ではUndoShadowSize 0となっているので,UNDOのShadowは完全にパージされている.GlobalFreePtrを実行しているのはfreeblock::delmemしかないし,この関数の中でしかMemoryBlockCountとMemoryBlockSizeは更新されていないのだから,間違いようがないとしか思えないのだが…

単にファイルを開いて閉じるではこのエラーは発生しないので,カード削除とそれに伴うUNDO処理にからんだ問題であることは確かだが… ⇒いや,少なくともUNDOとは関わりがないのではないか?UNDOはCOUPLING::EraseFamilyTreeの中ですでにパージされている.上で,

NringTotal:1180+CanTotal:7023434+UndoShadowSize=7024614

としているが,間違いだ.プリント文の誤記で,実際は

NringTotal:1180+CanTotal:7023434+UndoShadowSize=0 = 7024614

でUndoShadowSize=0となっている.つまり,UNDOのShadowはすでにパージ完了している.何度数え直してもゴミ箱の中は個数で10301,サイズで7023434だ.結局,ThrowCanのどこかで漏れが発生していると考えるしかない.⇒成仏しきれない亡者を9名発見した.CARDLINKが7つ,MARGLINKが2つ.deleteでメモリからパージされるためにはNODULEのoperator deleteが起動されなくてはならないが,何かの理由でそうならなかったのだろう.⇒NODULE::operator deleteには来ているが,LIFEが空になっていないためパージを免れている.

大体の様子はつかめた.これらのノードはアプリ終了時,EraseFamilyTreeでLINKTABLE::ClearTableによって削除されている.ただし,このときはShadow付きであるためゴミ箱には直行せず,Nringの中で保留状態になっている.この後のresetUndoChainでゴミ箱に移動しているようだ.CallSetCouplingPtrがアプリから呼び出されるときには,すでにNringにはコア骨格木しか残っていない.

resetUndoChainには明示的に関係オブジェクトをゴミ箱に移すようなコードは存在しない.単にコマンドチェーンのUNDOCHAINを1個づつdeleteしているだけだ.とすれば,UNDONODE:Disposeしかない.⇒確かにそのようだ.ここではUNDONODEが参照している実ノードをDumpWasteでゴミ箱に投入している.しかも,その前にsetNring(address, NRING_DELETE)を実行しているのだから,息の根が止まってもよさそうなものだが… ⇒setNringではShadowが残っているため,Shadow->DELETED = DEADとしている.もし,Shadowが付いていなければLIFEに空がセットされているのだが…

UNDOチェーンの長さは有限なので満杯になると後ろからチェーンを切断するような動作になっているはずだ.従って,UNDONODEが削除されたことで参照している実ノードが削除されたことには必ずしもならないというのは正しいと思われるが,現行ではゴミ箱に入るときはすでに死亡しているという前提なので矛盾が生じる.どう解決すればよいか?

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA