DOCloudCache.cc 6.92 KB
Newer Older
1
2
3
4
5
6
7
8
//== INCLUDES =================================================================

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

//=============================================================================
#include "DOCloudCache.hh"
9
10
#include "DOCloudConfig.hh"

11
12
13
14
15
16
17
#include <Base/Utils/OutcomeUtils.hh>
#include <Base/Debug/DebOut.hh>

#include <fstream>
#include <iomanip>
#include <cctype>
#include <functional>
18
#include <sstream>
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

#include <boost/filesystem.hpp>

#include <windows.h>

DEB_module("DOCloudCache")

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

namespace DOcloud {

namespace {

// 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.
97
std::string string_to_hash(const std::string& _str)
98
99
{
  const std::hash<std::string> hash_fn;
100
101
102
  std::stringstream strm;
  strm << std::hex << hash_fn(_str);
  return strm.str();
103
104
}

105
106
const size_t NO_SOLUTION_CODE = UINT_MAX;

107
108
109
110
111
112
113
114
115
116
// 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;
117
118
119
120
121
122
  if (dim == NO_SOLUTION_CODE)
  {
    _x.clear();
    return true;
  }

123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
  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;

141
142
143
144
145
  if (_x.empty())
  {
    out_file_strm << NO_SOLUTION_CODE;
    return true;
  }
146
147
148
149
150
151
152
153
154
  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

155
156
157
158
159
160
Cache::Cache(const std::string& _mip_lp) 
  : mip_lp_(_mip_lp), hash_(string_to_hash(mip_lp_)), found_(false) 
{
  DEB_enter_func;
  DEB_line(2, "Cache hash: " << hash_);
}
161

162
bool Cache::restore_result(std::vector<double>& _x, double& _obj_val) 
163
{
164
  DEB_enter_func;
165
166
  const auto* cache_loc = Config::query().cache_location();
  if (cache_loc == nullptr) // cache location not provided, disabale the cache
167
    return false;
168

169
170
  for (size_t iter_nmbr = 0; iter_nmbr < 10; ++iter_nmbr)
  {
171
    filename_ = cache_loc + hash_ + '_' + std::to_string(iter_nmbr);
172

173
    std::string dat_filename(filename_ + ".dat");
174
175
176
177
178
179
180
181
182
183
184
    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;
    }

185
    if (FileLock::active(filename_))
186
187
188
      break;

    std::string cache_cnts;
189
    if (!load_file(filename_ + ".lp", cache_cnts))
190
191
      break;

192
    if (cache_cnts == mip_lp_)
193
    {
194
      found_ = load_data(filename_ + ".dat", _x, _obj_val);
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
      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.
215
    for (const auto& filename : used_files_)
216
    {
217
218
      if (!filename.empty())
        std::remove(filename.c_str());
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
    }
  }

  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;
251
252
  THROW_OUTCOME_if(found_, TODO); /* Multiple store of DOcloud cache value. */
  if (!filename_.empty())
253
254
  {
    CacheSaver saver;
255
    saver.save(filename_, _x, _obj_val, mip_lp_);
256
257
258
259
260
261
262
263
264
265
  }
}

} // namespace DOcloud

} // namespace COMISO

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