}
}
+static unsigned int mail_cache_count_alive_headers(struct mail_cache *cache)
+{
+ unsigned int count = 0;
+ for (unsigned int index = 0; index < cache->fields_count; ++index) {
+ if (cache->fields[index].field.type == MAIL_CACHE_FIELD_HEADER &&
+ (cache->fields[index].field.decision &
+ ENUM_NEGATE(MAIL_CACHE_DECISION_FORCED)) !=
+ MAIL_CACHE_DECISION_NO)
+ ++count;
+ }
+ return count;
+}
+
+bool mail_cache_headers_check_capped(struct mail_cache *cache)
+{
+ struct mail_index_cache_optimization_settings *set =
+ &cache->index->optimization_set.cache;
+
+ if (set->max_headers_count == 0) return FALSE;
+ if (cache->headers_capped) return TRUE;
+
+ unsigned int count = mail_cache_count_alive_headers(cache);
+ cache->headers_capped = count >= set->max_headers_count;
+ return cache->headers_capped;
+}
+
+static struct event_passthrough *
+mail_cache_decision_rejected_event(struct mail_cache *cache, unsigned int field,
+ const char *reason)
+{
+ return event_create_passthrough(cache->event)->
+ set_name("mail_cache_decision_rejected")->
+ add_str("field", cache->fields[field].field.name)->
+ add_str("reason", reason);
+}
+
void mail_cache_decision_add(struct mail_cache_view *view, uint32_t seq,
- unsigned int field)
+ unsigned int field, bool *rejected_r)
{
struct mail_cache *cache = view->cache;
struct mail_cache_field_private *priv;
i_assert(field < cache->fields_count);
+ *rejected_r = FALSE;
+
if (view->no_decision_updates)
return;
}
/* field used the first time */
- if (priv->field.decision == MAIL_CACHE_DECISION_NO)
+ if (priv->field.decision == MAIL_CACHE_DECISION_NO) {
+ if (mail_cache_headers_check_capped(view->cache)) {
+ *rejected_r = TRUE;
+
+ const char *reason = "too_many_headers";
+ struct event_passthrough *e =
+ mail_cache_decision_rejected_event(
+ cache, field, reason);
+ e_debug(e->event(),
+ "Cache rejected header '%s': %s",
+ priv->field.name, reason);
+ return;
+ }
+
priv->field.decision = MAIL_CACHE_DECISION_TEMP;
+ }
priv->field.last_used = ioloop_time;
priv->decision_dirty = TRUE;
cache->field_header_write_pending = TRUE;
internal fields and for mail_*cache_fields settings? */
initial_registering = cache->file_fields_count == 0;
+ if (newfield->decision == MAIL_CACHE_DECISION_NO) {
+ /* reset the cached flag, it will be set again anyway on first
+ access if that is the case */
+ cache->headers_capped = FALSE;
+ }
+
orig = &cache->fields[newfield->idx];
if ((newfield->decision & MAIL_CACHE_DECISION_FORCED) != 0 ||
((orig->field.decision & MAIL_CACHE_DECISION_FORCED) == 0 &&
return -1;
}
+ /* reset the cached flag, it will be set again anyway on first access if
+ that is the case */
+ cache->headers_capped = FALSE;
+
new_fields_count = field_hdr->fields_count;
if (new_fields_count != 0) {
cache->file_field_map =
larger parts of it). This is used with MAIL_INDEX_OPEN_FLAG_SAVEONLY
to avoid unnecessary cache reads. */
bool map_with_read:1;
+ /* Cache headers count has been capped */
+ bool headers_capped:1;
};
struct mail_cache_loop_track {
mail_cache_decision_changed_event(struct mail_cache *cache, struct event *event,
unsigned int field);
+bool mail_cache_headers_check_capped(struct mail_cache *cache);
+
struct mail_cache_purge_drop_ctx {
struct mail_cache *cache;
time_t max_yes_downgrade_time;
read. */
mail_cache_transaction_refresh_decisions(ctx);
- mail_cache_decision_add(ctx->view, seq, field_idx);
+ /* The decision to reject a field may come last minute during actual add,
+ for example if the header count limit has been reached */
+ bool rejected;
+ mail_cache_decision_add(ctx->view, seq, field_idx, &rejected);
+ if (rejected)
+ return;
fixed_size = ctx->cache->fields[field_idx].field.field_size;
i_assert(fixed_size == UINT_MAX || fixed_size == data_size);
mail_cache_field_get_decision(struct mail_cache *cache, unsigned int field_idx);
/* Notify the decision handling code when field is committed to cache.
If this is the first time the field is added to cache, its caching decision
- is updated to TEMP. */
+ is updated to TEMP. Sets rejected to true if header count limit has been
+ reached and the field has NOT been switched to TEMP */
void mail_cache_decision_add(struct mail_cache_view *view, uint32_t seq,
- unsigned int field);
+ unsigned int field, bool *rejected_r);
/* Set data_r and size_r to point to wanted field in cache file.
Returns 1 if field was found, 0 if not, -1 if error. */
set->cache.purge_header_continue_count;
if (set->cache.record_max_size != 0)
dest->cache.record_max_size = set->cache.record_max_size;
+
+ dest->cache.max_headers_count = set->cache.max_headers_count;
}
void mail_index_set_ext_init_data(struct mail_index *index, uint32_t ext_id,
/* If cache record becomes larger than this, don't add it. */
unsigned int record_max_size;
+ /* Maximum number of headers to cache */
+ unsigned int max_headers_count;
+
/* Maximum size for the cache file. Internally the limit is 1 GB. */
uoff_t max_size;
/* Never purge the file if it's smaller than this */
if (_mail->lookup_abort == MAIL_LOOKUP_ABORT_NOT_IN_CACHE_START_CACHING &&
mail_cache_field_get_decision(_mail->box->cache, field_idx) ==
MAIL_CACHE_DECISION_NO) {
+ bool rejected ATTR_UNUSED;
mail_cache_decision_add(_mail->transaction->cache_view,
- _mail->seq, field_idx);
+ _mail->seq, field_idx, &rejected);
}
return ret;
.cache = {
.unaccessed_field_drop_secs = set->mail_cache_unaccessed_field_drop,
.record_max_size = set->mail_cache_record_max_size,
+ .max_headers_count = set->mail_cache_max_headers_count,
.max_size = set->mail_cache_max_size,
.purge_min_size = set->mail_cache_purge_min_size,
.purge_delete_percentage = set->mail_cache_purge_delete_percentage,
DEF(STR, mail_server_admin),
DEF(TIME_HIDDEN, mail_cache_unaccessed_field_drop),
DEF(SIZE_HIDDEN, mail_cache_record_max_size),
+ DEF(UINT_HIDDEN, mail_cache_max_headers_count),
DEF(SIZE_HIDDEN, mail_cache_max_size),
DEF(UINT_HIDDEN, mail_cache_min_mail_count),
DEF(SIZE_HIDDEN, mail_cache_purge_min_size),
.mail_cache_min_mail_count = 0,
.mail_cache_unaccessed_field_drop = 60*60*24*30,
.mail_cache_record_max_size = 64 * 1024,
+ .mail_cache_max_headers_count = 100,
.mail_cache_max_size = 1024 * 1024 * 1024,
.mail_cache_purge_min_size = 32 * 1024,
.mail_cache_purge_delete_percentage = 20,
unsigned int mail_cache_min_mail_count;
unsigned int mail_cache_unaccessed_field_drop;
uoff_t mail_cache_record_max_size;
+ unsigned int mail_cache_max_headers_count;
uoff_t mail_cache_max_size;
uoff_t mail_cache_purge_min_size;
unsigned int mail_cache_purge_delete_percentage;