DOCloudCache.cc 7.28 KB
Newer Older
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
//== INCLUDES =================================================================

//== COMPILE-TIME PACKAGE REQUIREMENTS ========================================
#include <CoMISo/Config/config.hh>
#if COMISO_DOCLOUD_AVAILABLE

//=============================================================================
#include "DOCloudCache.hh"
#include <Base/Utils/OutcomeUtils.hh>
#include <Base/Debug/DebOut.hh>

#include <fstream>
#include <iomanip>
#include <cctype>
#include <functional>

#include <boost/filesystem.hpp>

#include <windows.h>

DEB_module("DOCloudCache")

//== NAMESPACES ===============================================================
namespace COMISO {

namespace DOcloud {

namespace {

const std::string& cache_directory()
{
  static std::string cache_dir = 
    "\\\\camfs1\\General_access\\Martin_Marinov\\ReForm\\Cache\\";
  static bool already_read = false;
  if (!already_read)
  {
    already_read = true;
    const char* env_cache_dir = getenv("ReFormCacheDir");
    if (env_cache_dir != nullptr && env_cache_dir[0] != 0)
    {
      cache_dir = env_cache_dir;
      if (cache_dir.back() != '\\')
        cache_dir += '\\'; // Eventually add '\' to the directory string.
    }
  }
  return cache_dir;
}

// 
std::string full_cache_filename(const std::string& _filename)
{
  return cache_directory() + _filename;
}

// Create a new temporary exclusive file without extension that is used to 
// prevent write or read operation on files with the same name and extension
// .lp or .dat. while the cache is being written. This is the only class that
// uses windows specific APIs.
class FileLock
{
public:
  FileLock(const std::string& _filename)
  {
    file_hnd_ = CreateFile(_filename.c_str(),
      GENERIC_WRITE,
      0,            // ShareMode - 0 prevents any sharing
      nullptr,      // SecurityAttributes
      CREATE_NEW,   // Fails if file already exists.
      FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, // File attributes.
      NULL);
  }

  // We can write the DoCloud results only id the lock is successful.
  bool sucess() const { return file_hnd_ != INVALID_HANDLE_VALUE; }

  // If there is an active lock we can not read the related data because they 
  // are being written.
  static bool active(const std::string& _filename)
  {
    return GetFileAttributes(_filename.c_str()) != INVALID_FILE_ATTRIBUTES;
  }

  // The destructor removes the lock. from this moment the data can be freely
  // read and there should not be anyone who tries to rewrite them.
  ~FileLock()
  {
    if (sucess())
      CloseHandle(file_hnd_); // This will delete the file.
  }

private:
  HANDLE file_hnd_;
};


bool load_file(const std::string& _filename, std::string& _file_cnts)
{
  std::ifstream in_file_strm(_filename, std::ios::ate);
  if (!in_file_strm.is_open())
    return false;
  _file_cnts.reserve(in_file_strm.tellg());
  in_file_strm.seekg(0, std::ios::beg);
  _file_cnts.assign(std::istreambuf_iterator<char>(in_file_strm),
                    std::istreambuf_iterator<char>());
  return true;
}

bool save_file(const std::string& _filename, const std::string& _file_cnts)
{
  std::ofstream out_file_strm(_filename);
  if (!out_file_strm.is_open())
    return false;
  out_file_strm << _file_cnts;
  return true;
}

// Finds a key string from the file name. This string will be used as file name
// where to store the related cached data.
void string_to_key(const std::string& _str, std::string& _key)
{
  // 1. Writes the file length.
  const std::hash<std::string> hash_fn;
  _key = std::to_string(hash_fn(_str));
}

// Load variables and objective values from a file.
bool load_data(const std::string& _filename, 
               std::vector<double>& _x, double& _obj_val)
{
  std::ifstream in_file_strm(_filename);
  if (!in_file_strm.is_open())
    return false;

  size_t dim = std::numeric_limits<size_t>::max();
  in_file_strm >> dim;
  if (dim != _x.size())
    return false;

  for (auto& xi : _x)
    in_file_strm >> xi;
  in_file_strm >> _obj_val;
  return !in_file_strm.bad();
}

// Store variables and objective values in a file.
bool save_data(const std::string& _filename, 
               const std::vector<double>& _x, const double& _obj_val)
{
  std::ofstream out_file_strm(_filename);
  out_file_strm << std::setprecision(std::numeric_limits<double>::digits10 + 2);
  if (!out_file_strm.is_open())
    return false;

  out_file_strm << _x.size() << std::endl;
  for (const auto& xi : _x)
    out_file_strm << xi << std::endl;;
  out_file_strm << _obj_val;
  return !out_file_strm.bad();
}

} // namespace


bool Cache::restore_result(
  std::string& _lp_prbl_str, // string containing the .lp problem
  std::vector<double>& _x,   // result.
  double& _obj_val)          // objective function value.
{
  found_ = false;
  lp_file_cnts_ = std::move(_lp_prbl_str);
  if (cache_directory().empty())
    return false;
  string_to_key(lp_file_cnts_, key_);
  key_ += '_';
  for (size_t iter_nmbr = 0; iter_nmbr < 10; ++iter_nmbr)
  {
    last_filename_ = full_cache_filename(key_) + std::to_string(iter_nmbr);

    std::string dat_filename(last_filename_ + ".dat");
    boost::system::error_code err_cod;
    if (!boost::filesystem::exists(
          boost::filesystem::path(dat_filename.c_str()), err_cod) ||
         err_cod.value() != boost::system::errc::success)
    {
      // If the .dat file does not exist it is not safe to check the lock because
      // it is possible that this process finds no lock, another process sets the
      // lock and start writing data, this process reads not fully written data.
      break;
    }

    if (FileLock::active(last_filename_))
      break;

    std::string cache_cnts;
    if (!load_file(last_filename_ + ".lp", cache_cnts))
      break;

    if (cache_cnts == lp_file_cnts_)
    {
      found_ = load_data(last_filename_ + ".dat", _x, _obj_val);
      return found_;
    }
  }
  return false;
}

namespace {
// Save the couple of files fname.lp and fname.dat . They are an element of a 
// sort of map from file.lp to file.dat. So if there is an error saving the .dat
// file, the .lp file must also e deleted.
class CacheSaver
{
public:
  CacheSaver() : success_(false) {}

  ~CacheSaver()
  {
    if (success_)
      return;
    // Removes files eventually written if there has been any kind of failure.
    for (const auto& f_name : used_files_)
    {
      if (!f_name.empty())
        std::remove(f_name.c_str());
    }
  }

  void save(const std::string& _filename, const std::vector<double>& _x,
    const double& _obj_val, const std::string& _lp_cnts)
  {
    DEB_enter_func;

    FileLock file_lock(_filename);
    if (file_lock.sucess())
    {
      used_files_[0] = _filename + ".lp";
      success_ = save_file(used_files_[0], _lp_cnts);
      if (success_)
      {
        used_files_[1] = _filename + ".dat";
        success_ = save_data(used_files_[1], _x, _obj_val);
      }
    }
  }

private:
  bool success_;
  std::string used_files_[2];
};

} // namespace

void
Cache::store_result(const std::vector<double>& _x, const double& _obj_val)
{
  DEB_enter_func;
  THROW_OUTCOME_if(found_, TODO); /* Multiple store of Docloud cache value. */
  if (!last_filename_.empty())
  {
    CacheSaver saver;
    saver.save(last_filename_, _x, _obj_val, lp_file_cnts_);
  }
}

} // namespace DOcloud

} // namespace COMISO

#endif // COMISO_DOCLOUD_AVAILABLE
//=============================================================================