Слишком глубокая ошибка уровня стека в родном расширении ruby, как уменьшить уровень стека?

У меня есть API Ruby on Rails, который обрабатывает простой вызов API и возвращает некоторые зашифрованные данные. Шифрование выполняется на C++ с использованием нативного C API для ruby. (ссылка здесь).

Нативная часть отлично работает при компиляции и линковке как отдельная программа, а также при использовании с ruby ​​в IRB.

Однако, когда я использую его из API Rails, я иногда получаю сообщение об ошибке "Слишком глубокий уровень стека".

Ошибка появляется или нет в зависимости от размера обрабатываемых данных.

Согласно этому ответу, "уровень" стека на самом деле представляет собой пространство стека, поэтому было бы разумно, если бы у меня было больше данных для обработки, тогда у меня больше данных в стеке, поэтому он заполняется быстрее и т. д...

Сначала я оставил все свои переменные в стеке для простоты и чтобы не забыть освободить выделенную память. Увидев эту ошибку, я переключился на подход с динамическим размещением. Однако, вопреки тому, что я ожидал, слишком глубокая ошибка уровня стека возникает для еще меньшего размера данных.

data_controller.rb

  def load_data data_path, width
    authorize!                                                                                                                                                               

    encrypted = NativeDataProtector.encrypt(data_path, get_key(), get_iv())
    return [ encrypted, "application/octet-stream" ]
  end

native_encryptor.cpp

VALUE encrypt_n(VALUE _self, VALUE data_path, VALUE key, VALUE salt){
  DataProtector protector;
  string *b64 = protector.encrypt(StringValueCStr(data_path),   \
                 StringValueCStr(key),   \
                 StringValueCStr(salt));

    VALUE ret = rb_str_new(b64->c_str(), b64->length());
    delete(b64);
  return ret;
}

extern "C" void Init_data_protector() {
  VALUE mod = rb_define_module("NativeDataProtector");
  rb_define_module_function(mod, "encrypt", (VALUE(*)(ANYARGS))encrypt_n, 3);
}

зашифровать.h

#include <ruby.h>
#include "extconf.h"

#include <iostream>
#include <fstream>
#include <vector>
#include <list>

#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>

class DataProtector {

 private :
  int pad_cleartext(vector<unsigned char> *cleartext);
  vector<unsigned char> *read_data(string path);
  int aes_encrypt(vector<unsigned char> *plaintext, string key,
          string iv, unsigned char *ciphertext);
  string to_b64(unsigned char* in);
  void handleErrors(void);

 public :
  string *encrypt(string data_path, string key, string salt);
};

шифровать.cpp

string *DataProtector::encrypt(string data_path, string key, string salt) {
  vector<unsigned char> *cleartext = readData(data_path);
  int length = pad_cleartext(cleartext);

  unsigned char* output = new unsigned char[length + 16];

  int ciphertext_len;

  // encrypt
  string *encrypted = new string("");
  ciphertext_len = aes_encrypt(&((*cleartext), key, iv, output);
  (*encrypted) += to_b64(output);

  delete(cleartext);
  delete(output);

  return encrypted;
}

int DataProtector::aes_encrypt(vector<unsigned char> *plaintext, string key,
  string iv, unsigned char *ciphertext)
{
  EVP_CIPHER_CTX *ctx;

  int len;

  int ciphertext_len;

  /* Create and initialise the context */
  if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

  /* Initialise the encryption operation. IMPORTANT - ensure you use a key
   * and IV size appropriate for your cipher
   * In this example we are using 256 bit AES (i.e. a 256 bit key). The
   * IV size for *most* modes is the same as the block size. For AES this
   * is 128 bits */
  if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, (const unsigned char *)key.c_str(), (const unsigned char *)iv.c_str()))
    handleErrors();

  /* Provide the message to be encrypted, and obtain the encrypted output.
   * EVP_EncryptUpdate can be called multiple times if necessary
   */
  if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, reinterpret_cast<unsigned char*>(plaintext->data()), plaintext->size()))
    handleErrors();
  ciphertext_len = len;

  /* Finalise the encryption. Further ciphertext bytes may be written at
   * this stage.
   */
  if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors();
  ciphertext_len += len;

  /* Clean up */
  EVP_CIPHER_CTX_free(ctx);

  return ciphertext_len;
}

int DataProtector::pad_cleartext(vector<unsigned char> *in) {
  // padds to length multiple of 16
  int nb_blocks = in->size() / 16 + ((in->size()%16 == 0)? 1:1);
  int size = nb_blocks*16;

  for (unsigned int i=in->size(); i<size; i++) {
    unsigned char c = '0';
    in->push_back(c);
  }
  return size;
}

vector<unsigned char> *DataProtector::read_data(string path) {
    streampos size;
    ifstream file(path, ios::binary);

    file.seekg(0, ios::end);
    size = file.tellg();
    file.seekg(0, ios::beg);

    vector<unsigned char> *data = new vector<unsigned char>(fileSize);
    file.read((char*) &data[0], size);
    return data;
}

void DataProtector::handleErrors(void) {
  ERR_print_errors_fp(stderr);
  abort();
}

(фактическое шифрование взято из здесь)

Трассировка стека ошибок, которую я получаю:

SystemStackError (stack level too deep):

app/controllers/data_controller.rb:41:in `encrypt'
app/controllers/data_controller.rb:41:in `load_data'
app/controllers/data_controller.rb:15:in `show'

Полагают, что причиной этой ошибки является слишком большой объем данных, выделенных в стеке, а не проблема рекурсии. Однако я не понимаю, почему переход на выделение кучи ничего не улучшил.

Я могу представить 2 решения:

  1. нарезка данных в ruby ​​и вызов нативного метода несколько раз с меньшим количеством данных.
  2. увеличение размера стека ruby. Однако оба эти решения неидеальны для моего проекта из-за проблем с производительностью/ресурсами.

Есть ли другой способ уменьшить использование стека моей программой?


person Vincent    schedule 14.10.2019    source источник
comment
Вероятно, где-то стек поврежден   -  person Alan Birtles    schedule 14.10.2019
comment
Как выглядит DataProtector?   -  person Botje    schedule 14.10.2019