DOCloudCache.cc 6.97 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
    }
  }

  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

247
void Cache::store_result(const std::vector<double>& _x, const double& _obj_val)
248
249
{
  DEB_enter_func;
250
251
252
253
  if (filename_.empty() || found_)
  {// restore_result() either not called at all, or hit the cache
    DEB_error("store_result() called incorrectly");
    return;
254
  }
255
256
  CacheSaver saver;
  saver.save(filename_, _x, _obj_val, mip_lp_);
257
258
259
260
261
262
263
264
}

} // namespace DOcloud
} // namespace COMISO

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