Слишком глубокая ошибка уровня стека в собственном расширении ruby, как уменьшить уровень стека?
У меня есть Ruby on Rails api, который обрабатывает простой вызов API и возвращает некоторые зашифрованные данные. Шифрование выполняется на C++ с использованием Ruby native C api. (ссылка здесь).
Собственная часть отлично работает при компиляции и компоновке в качестве отдельной программы, а также при использовании с ruby в IRB.
Однако, когда я использую его из Rails API, я иногда получаю ошибку "Уровень стека слишком глубокий".
Кажется, что ошибка возникает или нет, в зависимости от размера обрабатываемых данных.
Согласно этому ответу, уровень стека на самом деле является пространством стека, поэтому было бы логично, что если у меня будет больше данных для обработки, то у меня будет больше данных в стеке, поэтому он будет заполняться быстрее и т. Д.
Изначально я оставил все свои переменные в стеке для простоты и, чтобы не забыть освободить выделенную память. Увидев эту ошибку, я переключился на метод динамического распределения. Однако, вопреки тому, что я ожидал, слишком глубокая ошибка уровня стека возникает для еще меньшего размера данных.
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);
}
encrypt.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);
};
encrypt.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 решения:
- вырезать данные в ruby и несколько раз вызвать собственный метод с меньшим объемом данных.
- увеличение размера стопки рубинов. Однако оба этих решения неидеальны для моего проекта из-за проблем с производительностью / ресурсами.
Есть ли другой способ уменьшить использование стека моей программой?