Changement de données à la volée
Bonjour !
J'ai un service qui expose une classe COM à des clients.
La classe COM lit des données pour les fournir à ses clients.
Les données sont en lecture seule et sont mises à jour de temps en temps par d'autres processus, en bloc.
Je souhaite changer les données à la volée sans arrêter le service.
Supposons que les données soient gérées par une classe DataLinker:
Code:
1 2 3 4 5 6 7
| class DataLinker
{
public:
DataLinker(std::_tstring);
bool Open();
...
}; |
Voici la classe DataLocker qui maintient les données en disponibilité, tant qu'au
moins un client les utilise ou tant qu'il n'y a pas de nouvelles données disponibles,
grâce à un compteur de référence:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| class DataLocker
{
public:
DataLocker() = default;
private:
DataLocker(DataLocker const &) = delete;
DataLocker & operator=(DataLocker const &) = delete;
DataLinker *f_ptr_DataLinker = nullptr;
std::_tstring f_datapath;
LONG f_refcnt = 0l;
public:
DataLinker *GetDataLinker()
{ return f_ptr_DataLinker; }
std::_tstring const & GetDataPath() const
{ return f_datapath; }
bool IsEmpty() const
{ return f_ptr_DataLinker == nullptr; }
void AddRef()
{
InterlockedIncrement(&f_refcnt);
}
void Release()
{
if (InterlockedDecrement(&f_refcnt) == 0)
Unload();
}
bool Load(std::_tstring const & datapath)
{
ATLASSUME(f_ptr_DataLinker == nullptr && f_refcnt == 0l);
DataLinker *ptr_DataLinker;
try
{
ptr_DataLinker = new DataLinker(datapath);
if (ptr_DataLinker == nullptr || !ptr_DataLinker->Open())
{
delete ptr_DataLinker;
return false;
}
f_ptr_DataLinker = ptr_DataLinker;
f_refcnt = 1l;//Increment(&f_refcnt);
f_datapath = datapath;
return true;
}
catch (std::exception & e)
{
#ifdef _DEBUG
OutputDebugStringA(e.what());
#endif
delete ptr_DataLinker;
}
return false;
}
private:
void Unload()
{
ATLASSUME(f_ptr_DataLinker != nullptr && f_refcnt == 0l);
delete f_ptr_DataLinker;
f_ptr_DataLinker = nullptr;
//f_refcnt = 0l;
f_datapath.clear();
}
}; |
Voici la classe singleton DataSwitcher qui gère la disponibilité et le changement de données (un pooler avec deux DataLocker pour passer de l'un à l'autre):
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| class DataSwitcher
{
private:
DataLocker f_pls1;
DataLocker f_pls2;
DataLocker *f_curpls = nullptr;
DataSwitcher()
{ CheckParams(); }
public:
void CheckParams();
static DataSwitcher & Get();
DataLocker *Data()
{ return f_curpls; }
};
void DataSwitcher::CheckParams()
{
//check if a DataLocker is available
DataLocker *next_pls = nullptr;
DataLocker *curr_pls = nullptr;
if (f_pls1.IsEmpty())
{
next_pls = &f_pls1;
curr_pls = &f_pls2;
}
else if (f_pls2.IsEmpty())
{
next_pls = &f_pls2;
curr_pls = &f_pls1;
}
if (next_pls)
{
unsigned somevalue = read_somevalue_somewhere();
std::_tstring datapath(_T("D:\\DATA_VERSION_") + std::to_tstring(somevalue) + _T("\\"););
if (datapath != curr_pls->GetDataPath())
{
//load...
if (next_pls->Load(datapath))
{
//... and switch
f_curpls = next_pls;
//release current
if (!curr_pls->IsEmpty())
curr_pls->Release();
}
}
}
}
DataSwitcher & DataSwitcher::Get()
{
static DataSwitcher ds;
return ds;
} |
Et pour finir voici le code à ajouter dans la classe COM, ou en début et fin d'utilisation de n'importe quel client:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| DataLocker *f_curpls = nullptr;
...
HRESULT FinalConstruct()
{
f_curpls = DataSingleton::Get().Data();
if (f_curpls == nullptr)
return E_FAIL;
f_curpls->AddRef();
return S_OK;
}
void FinalRelease()
{
if (f_curpls)
f_curpls->Release();
} |
La méthode DataSwitcher::CheckParams() est appélée une fois lors de la construction unique de DataSwitcher dans sa méthode static Get(), et puis de temps en temps dans un background thread qui appelle CheckParams().
J'ai trois doutes:
-le compteur de référence de DataLocker est-il safe, ne va-t-il jamais passer sous zéro, ou rester toujours strictement supérieur à zéro ?
-Est-ce que l'échange de DataLocker dans CheckParams() est safe, ainsi que son acquisition et incrémentation de référence dans le FinalConstruct() des clients ?
-En fin de programme, il reste un DataLocker disponible. Où faire le dernier Release() proprement ?
Voilà, merci, j'espère que j'ai été assez clair :coucou: